Back up before upgrading. This release includes additive database migrations
and storage configuration/safety changes. Existing S3-compatible buckets must
be provisioned before startup; review UPGRADE.md.
Added
-
Multipart Models. The Vault can group existing Models into named pieces
and alternatives, with independent tags and PDF, Markdown or image guides.
Adding or deleting a grouping never moves or deletes a Model, its files, or
its revisions; Models remain directly accessible and reusable in multiple
groupings. -
Quick restart from Settings. Administrators on supervised deployments
can now gracefully restart the PrintStash API without opening container
details. Official Compose stacks enable the action through their existing
unless-stoppedrestart policy; unsupervised source launches keep it hidden. -
Read-only remote libraries and guarded garbage collection. Reusable,
encrypted S3-compatible, WebDAV, and SFTP connections can discover external
files through bounded, resumable scans without writing back to the source.
Destructive cleanup now requires an immutable preview digest, a recent fully
verified independent backup, explicit approval, quarantine, and final
revalidation; scheduled maintenance can never approve a deletion plan. -
OpenDAL Library sources and remote backup replicas. S3 Library sources
now use the shared OpenDAL adapter, Google Drive joins S3 and WebDAV as a beta
read-only source, and reusable remote connections can serve Library sources,
replicate locally committed archives, or do both. Settings keeps these S3,
WebDAV, SFTP and Google Drive profiles together under Remote storage. Remote
restore verifies provider identity and SHA-256. Backup management now has its
own Settings section, accepts validated archive uploads, and can discover and
adopt existing archives from configured OpenDAL backup connections. Providers
without immutable delete identity are excluded from automatic retention and
Vault-GC evidence. -
Scheduled backups with per-mode destinations. Administrators can opt in
to one daily backup at a selected UTC time and independently choose which
enabled local or remote destinations receive manual or automatic copies.
Existing installations keep local storage selected after upgrade, but may
switch either mode to remote-only storage. Backup copies can be deleted from
Settings after exact-source verification. -
Reliable Google Drive backup uploads. Google Drive archives are now sent
through OpenDAL's one-shot writer, so backups larger than one transfer chunk
no longer fail after their first chunk. -
Guarded remote storage hardening. WebDAV publishes through atomic
MOVE/Overwrite: F; SFTP uses exclusive create with pinned host keys.
Both are Guarded: permanent deletion requires confirmation and scheduled
purge remains disabled. NAS-linked files stay user-owned and are never
deleted by vault ownership maintenance; restores require the same provider
and namespace. Azure and GCS remain deferred. -
Runtime-probed storage capability tiers and remote providers. Local and
S3 storage now report Verified, Guarded, or Unguarded guarantees from runtime
probes; durable publication intents and one-shot destructive confirmations
protect weaker backends. Setup and Settings add typed presets for S3, R2,
B2, Wasabi, self-hosted S3, Nextcloud, WebDAV, and SFTP. The full image pins
Apache OpenDAL core 0.58.2 and supports SFTP password or mounted-key
authentication (#90). -
GitHub-flavoured Markdown tables. Collection READMEs and Markdown
documents now render pipe-table syntax as semantic tables while retaining
the existing sanitisation boundary
(#88). -
Rich URL capture and Model Source records. Pending Imports can now accept
browser-transferred files, show complete/partial outcomes, retry only failed
or partial captures, and preserve per-Artifact source snapshots, confirmed or
inferred fields, and explicit user overrides. Portable library archives carry
optional provenance sidecars while remaining compatible with earlier exports. -
Authenticated marketplace capture in the browser extension. Printables
file selection and MakerWorld package selection now use the browser's own
authenticated session, with guided pairing, revocable named devices,
localhost permission setup, build identification, and clear partial/failure
states. PrintStash does not receive the marketplace session cookies. -
Capture provider connections and browser pairing. Per-user MyMiniFactory
OAuth and Cults metadata connections, plus named and revocable paired browser
devices, support source-aware capture without sharing source-site sessions. -
Bambu LAN reproducibility evidence. Externally started Bambu jobs now
preserve the task, subtask, project, profile, plate, layer, nozzle, and G-code
identities the printer actually reports. PrintStash makes a bounded,
best-effort capture of exact G-code or project 3MF files while they remain in
the printer's FTPS cache, labels exact/metadata/basic evidence honestly, and
can preview archived G-code contained in a captured 3MF
(#69,
#70,
#82).
Security
- Delegated printer administrators can no longer redirect provider endpoints or
forward stored credentials, view-only shares no longer expose original G-code,
blank JWT secrets are replaced automatically, and optional Postgres, SeaweedFS,
and migration MinIO services stay off host interfaces by default. - Capture rejects URL credentials and strips secret-shaped URL parameters. It
does not retain raw HTML, source-site cookies, OAuth codes, signed download
URLs, resolved credentials, or staging paths; active provider credentials are
encrypted at rest and never returned. Provider traffic is restricted to
bounded, validated endpoints.
Changed
- External storage is now presented as Library sources. Settings uses one
ownership-aware flow for mounted folders and read-only S3, WebDAV, and SFTP
connections, with explicit no-copy and no-delete guarantees throughout the UI. - Legacy S3 backup fields leave Settings. Storage now contains only managed
Vault storage and local backup retention. Existing legacy configuration remains
readable for upgrades, while new remote profiles live under Remote storage. - Multipart sets now live in the main Models library. The default Organized
view shows a set once without duplicating its member cards; Everything,
Multipart sets only and Parts only make every reusable Model accessible without a
second navigation mode. Search always reveals matching member Models, and
multipart cards use the same square visual language, tag actions and per-user
favorites as ordinary Models. Set-owned covers may be uploaded privately from
the user's computer or use an external HTTP(S) image without depending on a
member preview. Set details retain their read-only overview, an empty-state Add
part action and an explicit editor for metadata, tags, cover, ordered pieces,
variants and guides. - Model tags are now discoverable from the library. Editable model cards
and detail headers expose a direct Add/Edit tags action, where users can find
existing tags or create and assign a new one without entering full model edit. - Thumbnail generation now shares one versioned visual recipe. Full,
streaming, fallback, embedded 3MF, and browser preview paths use deterministic
framing and an upper-hemisphere camera. Durable recipe/hash caching coalesces
concurrent work across instances, enforces database-backed render slots, and
prevents repeated CPU/GPU cost for ready or deterministically failed results.
Screenshots render on demand from the visible scene without retaining the
drawing buffer, reject empty captures, and obey texture and 16-megapixel caps. - The browser extension now shows its normal SemVer release. The popup header
displays the packaged0.x.yversion, and every browser manifest derives that
version from the extension package so release identifiers cannot drift. - Storage ownership is data-plane only. PrintStash no longer creates S3
buckets or changes lifecycle policies. It inspects versioning/lifecycle state
to report safety, keeps backup S3 configuration independent, and requires an
explicit acknowledgement before starting on Unguarded storage. Legacy
VAULT_STORAGE_BACKEND/S3 settings remain accepted; new deployments use typed
provider configuration. - Dense STL previews preserve surface coverage. Oversized binary and ASCII
meshes now use an isolated, bounded two-pass streaming renderer before the
deterministic sampled fallback, keeping previews coherent without loading the
full mesh into API memory (#67). - Embedded 3MF previews take precedence. Valid slicer-provided PNG previews
bypass mesh regeneration, while invalid or oversized candidates are skipped
safely in favour of the next bounded candidate. - Authenticated package downloads moved out of the API. Server-side
marketplace authentication has been retired in favour of paired-browser
transfer, keeping source-site credentials in the user's browser. - Shared domain and UI packages now own reusable behaviour. Archive, G-code,
mesh, networking, notification, import, and printer logic is consolidated in
printstash-core; frontend domain helpers and UI primitives live in internal
workspace packages. Application startup now composes storage, queues,
realtime, sessions, and printer providers explicitly while retaining the
local-first defaults and existing API contracts. - Frontend toolchain moved to oxc. ESLint and
typescript-eslintare
replaced by oxlint, formatting is now enforced by
oxfmt (the frontend was previously unformatted), and
typechecking runs on the TypeScript 7 native compiler — about 6x faster
(3.97sto0.66son this tree). Droppingtypescript-eslintis what makes
TypeScript 7 usable at all: it has no stable programmatic API until 7.1.
pnpm lintandpnpm typecheckkeep their names;pnpm formatand
pnpm format:checkare new andformat:checkgates CI. - Lint rules now reject low-evidence typing. The
anti-slop rule set is vendored at
frontend/tools/oxlint/anti-slop/and enabled as errors, alongside oxlint's
React and Vitest rules.react/set-state-in-effect, previously disabled, is
now enforced. - The i18n coverage test parses JSX with
oxc-parserinstead of the TypeScript
compiler API, which TypeScript 7 no longer exposes. - Tests now mirror production ownership. Backend tests are separated into
unit, integration, live-contract, and end-to-end lanes; frontend app, shared
packages, browser extension, mock-API Playwright, real-backend Playwright,
storage-provider E2E, migration convergence, and branch-coverage floors are
independent gates. A Docker pre-release harness covers PostgreSQL, S3,
Spoolman, Authentik, and printer emulators, and arm64 images build on native
runners.
Fixed
- Vault Audit now explains why unlinked files are retained, shows their type,
size, and modification time when available, totals the space they may reclaim
after review, and makes clear that marking a finding reviewed never deletes
its file. - Restoring a backup now safely reuses storage objects whose size and SHA-256
already match the archive, so an in-place recovery no longer fails merely
because its immutable files are still present. A genuine destination
conflict still fails before any write, but now removes its preflight journal
and releases maintenance mode instead of leaving the vault read-only. If an
interrupted restore outlives the browser session, administrators can sign in
through an access-only recovery session and resume it without admitting a
concurrent database write. Container restarts preserve valid local ownership
receipts, and backups affected by an earlier metadata-only ownership repair
can restore after exact SHA-256 and inode verification. - Interrupted OpenDAL backup publications can now reconcile their persisted
ownership receipt instead of failing while constructing the verification
copy. - Library view now remembers the user's selection, and new browsers default to
Everything instead of Organized. - Exploratory-QA fixes now keep live locale changes consistent, translate the
library result count, validate Model names, backup retention, and filament
costs visibly, identify remote backup destinations accurately, keep delete
and restore warnings distinct, and confirm preset deletion before removing
it. - Remote-only backups now restore archives containing both Artifact data and
generated thumbnails, and missing rebuildable remote derivatives no longer
abort backup creation with a provider-specificNotFounderror. - Full container images now register OpenDAL's advertised Google Drive
transport. Remote-connection tests align their controls at intermediate
widths and report unavailable provider support explicitly instead of an
internal server error. - Multipart Model editing now keeps part actions, variants, metadata, and the
Save action in the mobile flow. Model-detail previews reserve a larger touch
surface, keep their file label clear of viewer controls, and use 44px mobile
targets without handing drag gestures to the page. - Selecting None while editing a Model now removes its Collection
assignment instead of leaving the Model in its previous Collection
(#110). - PDF guides recover from browsers that cached the pdf.js worker before its
production MIME type was corrected and use PDF.js's compatibility worker for
mobile browsers that cannot start its modern build. Protected PDFs are passed
to the worker as downloaded bytes instead of Android-inaccessibleblob:
URLs; module-worker responses revalidate, and renderer failures report their
underlying error. - Backup restore confirmations now keep long source identities, namespaces,
and checksums contained within the dialog at desktop and mobile widths. - Saved light and dark theme preferences now survive a page refresh under the
production Content Security Policy. - MakerWorld package resolution accepts both current root-level and compatible
nested link responses, and Thingiverse model pages list and transfer selected
individual files instead of manufacturing a model ZIP candidate. - Browser-extension failures now lead with a short recovery step and keep safe
diagnostic codes in a collapsed technical-details section. - Cached 3D previews no longer leave their loading spinner over an already
rendered model when the same Model is reopened. - Manual backups no longer fail when a superseded thumbnail or another
rebuildable preview projection is absent from storage; original Artifact
bytes remain mandatory. - Completed uploads no longer leave a duplicate generic Import task polling
forever; clients also reconcile the stuck duplicate rows persisted by the
affected build. - Task Center reconnects now request every locally active job explicitly, so a
thumbnail rebuild or upload that finished while the browser was disconnected
reaches its terminal state instead of spinning forever after normal history
expires. Jobs the server genuinely no longer knows become retryable failures. - Completed ingestion now bypasses the legacy response cache when refreshing
the vault grid, outliner, and facets, so a newly uploaded Model appears
without a page reload. - Full, streaming, and sampled thumbnail renderers now take their material
albedo from the same versioned preview recipe, preventing large-STL previews
from looking like a different colour or material. - BGCODE uploads now work from every browser upload surface. Main uploads,
vault drag-and-drop, and revision uploads now accept PrusaSlicer.bgcode
files and route them through the existing binary G-code metadata and thumbnail
pipeline. - Uploads at exactly the configured per-file limit now reach the route's precise
upload_too_largeresponse instead of being rejected as an oversized request.
The API and reverse proxy retain a separate bounded multipart headroom above
that per-file cap
(#94). - API containers now honour positive numeric
PUIDandPGIDvalues for
bind-mounted data, repair ownership when that identity changes, and reject
invalid or root IDs before migrations start
(#83). - 3MF previews now preserve component and build transforms used by 3D Builder
projects when producing the browser's STL representation
(#84). - Pending Imports now preserve completed/partial outcomes across polling,
refreshes, retries, and cleanup; can be dismissed after capture without
affecting imported Models or Artifacts; block deletion while resolution is
active; recover legacy staging indexes; and no longer let expired terminal
jobs break a later upload. - Bambu LAN now uses persistent report-topic subscriptions without losing
request-topic commands, merges partial reports, reconciles evolving job
identities without conflating printers or print cycles, retains project-file
hints, and retries FTPS capture only for explicit transport faults. - Legacy local Artifacts can be adopted safely into the storage-ownership
ledger, while garbage collection and permanent deletion continue to preserve
bytes that PrintStash cannot prove it owns. - Legacy local and S3 backup archives can be discovered and explicitly adopted
after full validation without copying their bytes. Exact-source identity keeps
same-id replicas collision-safe across listing, restore, deletion, and
retention, while restore journals and interrupted S3 publication recovery now
fail closed on ambiguous archive or provider identity. - Fleet scheduling and printer queues now exclude deleted jobs, and fleet editor
selections are validated instead of being accepted through stale UI state. - Frontend hardening uncovered and fixed a missing-
starredModel-card crash,
an upload dialog that could overwrite the user's Collection choice after a
query resolved, and error-message lookups that could traverse object
prototypes. greenletis now declared directly, so the async database capability works
on Apple Silicon. SQLAlchemy requiresgreenletfor every asyncio code path
but declares it only forplatform_machineinAMD64,WIN32,aarch64,
amd64,ppc64le,win32,x86_64. macOS on Apple Silicon reportsarm64,
so that marker was false anduv syncleftgreenletout — an async engine
then failed at runtime on a developer Mac while passing in CI.aiosqlitealso
joins thedevgroup, so the SQLite async contracts run everywhere instead of
skipping (4 skipped tests became 4 passing ones).
Full changelog: v0.12.1...v0.13.0