Skip to content

Releases: xiao-villamor/PrintStash

v0.11.1 — Vault audit, capture, per-printer access, and i18n completion

Choose a tag to compare

@xiao-villamor xiao-villamor released this 23 Jul 18:57
8aaf187

This patch-version release is an explicit exception to normal 0.x patch policy: it adds features and append-only database migrations. Back up before upgrading.

Added

  • Vault Maintenance. Superusers can run persisted Quick or Full audits, inspect severity-grouped findings, cancel hashing between objects, repair eligible thumbnail/Metadata/Revision findings, and verify backup archive structure and manifest membership.
  • Pending Imports and Quick Capture. URL captures persist across restarts with owner-scoped review, retry, Collection/tag assignment, archive/file selection, and a minimal Manifest V3 browser helper that sends only current page URL/title.
  • Structured Model filters. Artifact type, material, slicer, printer model, Revision status, print history/outcome, storage location, and upload date extend canonical URLs, facet counts, and Saved Views.
  • Per-printer access control. Superusers can grant view, print, control, or admin roles independently for each printer. Printer, fleet, live WebSocket, remote-file, queue, routing, and maintenance APIs enforce the effective role; connection details and unavailable actions stay hidden in the UI.

Changed

  • Backup creation now fails if a database-owned blob is unreadable, missing, changes size, or vanishes while streaming instead of silently producing a smaller archive.
  • Completed Spanish translations across the Model detail print-settings panel, card-metric and metadata-field pickers, and remaining Settings surfaces left over from 0.11.0's localization foundation.

Fixed

  • Clearing all Structured Model filters at once now removes every active filter and date range from the URL instead of leaving the last-applied one in place.
  • The mobile bottom navigation no longer pushes "Profiles" into the overflow "More" sheet for administrators; it stays one tap away alongside Vault, Pending, and Printers.

Security

  • Pending Import URLs strip secret-shaped query parameters and reject embedded URL credentials; persisted manifests and API responses omit staging paths, cookies, and resolved download credentials.
  • Vault-audit and repair endpoints are superuser-only, Pending Imports are owner-scoped, and facet counts use the same Collection RBAC scope as Model browsing.
  • Printer grants are checked server-side on every REST and WebSocket boundary. Printing also requires existing Collection edit access to the source Model, and API keys inherit their owning user's printer grants.

Full changelog: v0.11.0...v0.11.1

v0.11.0 — Fleet, Scheduling, SSO, PWA, and i18n

Choose a tag to compare

@xiao-villamor xiao-villamor released this 19 Jul 15:32

Added

  • Fleet queue and routing. Administrators can queue Vault G-code across configured printers using manual, default-printer, or deterministic least-busy routing, then reorder, reroute, cancel, and retry eligible failed dispatches.
  • Maintenance-aware scheduling. Printers can be placed in soft-drain mode, assigned one-off maintenance windows, and given a simple maintenance log without interrupting active prints.
  • Persistent background progress. Import and scan job status now survives application restarts; interrupted non-replayable work is marked failed and retryable instead of disappearing or hanging indefinitely.
  • Fleet operations UI. Printer management now includes Fleet, Queue, and Maintenance views, and Model send dialogs can add G-code to the fleet queue.
  • OIDC / SSO login. Optional generic OpenID Connect supports Authentik, Authelia, and similar providers with discovery, authorization-code PKCE, validated ID tokens, collision-safe just-in-time users, configurable admin-group mapping, fallback local login, and encrypted setup under Settings → SSO.
  • Installable offline shell. PrintStash now includes a web-app manifest and service worker so it can be installed as a PWA and reopen its cached shell when the server is temporarily unreachable.
  • Localization foundation. A typed, persisted locale system ships with English and Spanish catalogs, a global language switch, and translated login, desktop, mobile, profile, and Settings navigation surfaces.

Changed

  • Immediate sends and scheduled dispatches share one storage-to-provider transfer seam while preserving provider capability checks and local-first storage behavior.
  • Printer jobs expose additive routing, queue, provider identity, dispatch-attempt, and blocked-reason fields without removing existing history fields.
  • The previously planned 0.12 Auth and Platform scope is included in 0.11.0.
  • Fleet queue reads keep active work visible while limiting terminal history to 20 rows by default, with bounded pagination up to 100 rows in the UI and API.
  • The local TaskQueue now wakes database-backed fleet dispatch immediately while timeout polling preserves queued work across restarts without Redis or another external service.

Fixed

  • Moonraker WebSocket connections now send the required server.connection.identify handshake before subscribing when an API key is configured, instead of silently subscribing unauthenticated.
  • Bambu LAN MQTT commands wait for the printer's own report before treating a print/pause/cancel/resume as successful, and status queries read the pushed MQTT report instead of a nonexistent local HTTPS status endpoint.
  • PrusaLink file listing, job state, and start-print now match the real v1 API: recursive folder listings, telemetry read from printer fields, and starting a file via POST /api/v1/files/local/{path} instead of the OctoPrint-style select/print body.
  • Cancelling a printer job now also marks its active PrintStash job row as cancelled, instead of leaving it stuck in a started/printing/paused state after the provider cancel succeeds.

Security

  • Dispatches interrupted between provider upload/start and the final database write are marked as an unknown outcome and cannot be retried automatically, preventing accidental duplicate prints after restart.
  • OIDC ID tokens accept only asymmetric RS/ES signing algorithms, and HTTPS callback configurations force Secure state, nonce, verifier, and session cookies.

Operations

  • Prometheus now reports fleet jobs, blocked work, dispatcher outcomes, scheduler liveness, and last-tick time; authenticated health details expose the same scheduler state.
  • PWA registration bypasses HTTP caching for service-worker updates, replaces old cache generations, activates waiting workers, and revalidates cached static assets while preserving offline navigation fallback.

Full changelog: v0.10.0...v0.11.0

v0.10.0 — Library Workflow Polish

Choose a tag to compare

@xiao-villamor xiao-villamor released this 14 Jul 13:32
f78604b

Added

  • Bulk library editing. Select up to 500 Models to move collections, add or remove tags, move to trash, or update labels across selected G-code revisions with atomic permission preflight.
  • Saved views and favorites. Star Models per user and save common collection, tag, search, printer-presence, and favorites filters. Applying a saved view restores canonical URL filters.
  • Richer Artifact comparison. Compare any two live Artifacts within one Model, including file, mesh, slicer, material, revision, and measured print-outcome differences.
  • Portable library migration. Export a versioned ZIP containing accessible Models, original Artifact blobs, metadata, taxonomy, print history, favorites, and saved views; admins can safely import it into another installation with hash validation and deduplication.
  • Configured-root folder import. Admins can trigger a targeted external-library scan below an already configured root without granting access to arbitrary server paths.

Security

  • Portable imports reject path traversal, missing blobs, unsupported manifests, oversized archives, and SHA-256 mismatches before database or storage writes.

Changed

  • Model detail now includes actual print counts, success rate, duration, filament, and cost when comparing Artifacts.
  • Daily library workflow polish. Saved views now support search, recent ordering, update, rename, duplicate, and delete; mixed Model/folder selection adapts available bulk actions; destination search prevents invalid folder moves; sorting, compact density, keyboard shortcuts, recent folders, and undo reduce repetitive library work.
  • Collection and tag management now lives in Vault, model editing, upload, and Settings; the redundant Catalog page and navigation entry have been removed.

Fixed

  • Artifact comparison rounds percentages and filament totals to readable precision instead of exposing floating-point artifacts, and the vault sort picker now uses the shared styled menu.
  • Header and recent-folder menus now stay above adjacent vault surfaces instead of being obscured by sticky controls or the collection sidebar.

Full changelog: v0.9.0...v0.10.0

v0.9.0 — Provider maturity

Choose a tag to compare

@xiao-villamor xiao-villamor released this 12 Jul 22:15
21bc261

Added

  • PrusaLink local FDM support (beta). Connect modern PrusaLink with Digest
    username/password authentication or legacy installations with an API key.
    PrintStash supports live status, G-code upload, explicit start, remote file
    inventory/deletion, and pause/resume/cancel without using Prusa Connect cloud.
  • Elegoo Neptune 4-family setup. Neptune 4, Pro, Plus, and Max can be added
    through a dedicated preset backed by the existing Moonraker/Klipper provider,
    keeping one tested transport and an identifiable printer variant.
  • Elegoo Centauri Carbon support (beta). Original Carbon printers connect
    locally over SDCP/WebSocket, while Carbon 2 uses its access-code-protected
    local MQTT API. Both provide status and start/pause/resume/cancel controls.
    Upload and file inventory stay disabled because current firmware exposes no
    safe confirmed file API.
  • OctoPrint support (beta). Connect a local OctoPrint/OctoPi instance with
    an API key for live status, G-code upload, explicit start, remote file
    inventory/deletion, and pause/resume/cancel.
  • Provider-aware printer setup. The add-printer flow now offers Moonraker,
    Elegoo Neptune 4, PrusaLink, OctoPrint, and Bambu LAN with only the
    credentials required by each integration.
  • Printer model detection on the printer card. Bambu (from its serial
    prefix) and Elegoo Neptune 4/Centauri (from the setup preset) show a
    best-effort hardware model automatically. Any printer's model can be set or
    corrected from a curated picklist to avoid typos, with an "Other" option
    for custom builds.
  • Bambu LAN Vault send support (beta). PrintStash can upload plain-text
    G-code to a Bambu printer's local cache over implicit FTPS, then start the
    uploaded file through the local MQTT connection. Upload-only remains the
    default; starting requires the explicit Start print immediately option.
  • Provider-safe Bambu send guard. Bambu sends now verify that the printer
    reports an idle/ready state before creating a transfer job, so an active or
    unreachable printer is rejected without leaving a job stuck uploading.
  • Provider capability matrix. Printer integrations declare supported
    actions from one shared capability set (status, upload, start, controls,
    file inventory, raw G-code, and measured consumption). API diagnostics and
    frontend actions are derived from that data instead of provider-specific UI
    checks.
  • Provider conformance suite. Every printer integration is exercised by one
    shared test pack: it must register, reject incomplete credentials, refuse the
    actions it does not declare without touching the network, and keep transport
    failures behind the common provider error surface. Adding a printer backend is
    now a single small class, so future providers arrive faster and behave
    consistently from the first release.
  • Printer management refresh. Printer cards can optionally show model
    artwork, detected models can be corrected inline, and provider badges now use
    the shared visual language. Printer detail has a clearer idle/offline Status
    view, compact temperature telemetry, and a new Settings tab for editing names,
    groups, notes, model overrides, connection addresses, and provider credentials.
  • Dashboard-wide UI polish. Document pages now share one responsive width
    and heading pattern; Settings, Statistics, Profiles, Organize, navigation,
    empty states, errors, and destructive confirmations use the same semantic
    surfaces and controls. Browser tabs also identify the current PrintStash page.
  • Consistent motion and feedback. Model grids settle in with a short capped
    stagger, the batch toolbar rises and fades with selection, tabs move on the
    compositor, and modals, drawers, menus, and toasts use the same fast motion
    scale with symmetric close animations and reduced-motion support.

Fixed

  • Predictable reconnect behavior. Initial connection and subscription
    failures now use bounded exponential backoff, while a recovered provider
    clears its stale offline/error state on the first successful status update.

  • Bambu status failures reach the printer hub. Polling errors no longer stay
    hidden inside the provider loop, preventing stale online state during a LAN
    outage and allowing the shared reconnect policy to take over.

  • Mixed-provider send controls are capability-aware. The send dialog keeps
    upload-only printers selectable but disables Send & Print when any
    selected printer cannot start the uploaded file.

  • Consistent page width. Printers, Statistics, Profiles, Settings, Organize
    and printer detail each defined their own scroll container and content width,
    so navigating between them shifted the layout sideways. They now share one
    PageContainer primitive with a single content width.

  • The batch selection toolbar animates. Selecting models popped the toolbar
    into existence with no transition; it now rises and fades, and reverses
    cleanly when a selection is toggled rapidly.

  • Printer creation ignores duplicate submits. Rapidly pressing Add
    printer
    can no longer create multiple database rows and unexpectedly advance
    the printer URL ID.

  • Navigation and overlays are more accessible. Active navigation links now
    expose their current-page state, focus behavior is consistent, and shared
    confirmation dialogs replace browser-native destructive prompts.

Performance

  • Live job database writes are bounded. Unchanged progress updates are
    coalesced to a five-second interval. Three consecutive job-sync database
    failures open a per-printer circuit breaker with increasing retry delays,
    preventing a database outage from becoming a write storm.
  • Theme switching no longer restyles the whole document. The theme swap
    applied a transition to every element and pseudo-element including
    box-shadow; it is now limited to color properties on the standard timing
    scale. Toasts and the tab indicator were also moved onto GPU-composited
    properties and the shared motion tokens.

Ops

  • Node.js 24 is pinned for frontend development and release builds, matching
    CI and the production frontend image.
  • Provider documentation, limitations, diagnostics, manual hardware checks,
    and the public roadmap now describe the 0.9.0 capability surface.

Full changelog: v0.8.5...v0.9.0

v0.8.5 — audit-log security & release hardening

Choose a tag to compare

@xiao-villamor xiao-villamor released this 09 Jul 22:24

Security

  • Login and refresh are now rate-limited (10 requests/minute per IP,
    separately for each endpoint), reusing the same limiter the public share
    API already had.
  • Backup and restore are now audit-logged (backup.create,
    restore.start, restore.complete, restore.failed), closing the one gap
    in audit coverage — everything else already flowed through the ORM's
    change-tracking hook.
  • Reverse-proxy IP handling documented. If api sits behind a reverse
    proxy, set the new FORWARDED_ALLOW_IPS compose variable to the proxy's
    address so rate limiting and audit logs see the real client IP instead of
    the proxy's. See docs/known-limitations.md.
  • Audit-log diffs no longer leak secrets. The change-tracking hook wrote
    unredacted before/after values for every changed column, so printer API
    keys, S3/Spoolman/MakerWorld credentials, the auto-generated JWT secret, and
    password/token hashes were readable via GET /admin/audit. Redacted by
    field name going forward; a migration scrubs previously-captured rows.

Fixed

  • S3/R2 backends could fail to boot on first run. _ensure_bucket's
    missing-bucket check compared against a response field
    (Error.StatusCode) that a ClientError never sets, so the auto-create
    path never ran on a real S3-compatible store (including MinIO) — startup
    failed instead of creating the bucket. Found by adding integration tests
    against a real MinIO instance.
  • A rejected send-to-printer could leave a job stuck "uploading" forever.
    The print job was marked UPLOADING before checking the printer's provider
    supported the upload; a 409 on an unsupported provider left it orphaned in
    that state. The check now runs before the job is created.
  • "Send to printer" could keep a deleted revision selected in the picker
    after it was trashed elsewhere; it now falls back to the current
    recommended revision. Failed sends now show which printer failed and why,
    instead of only a success count.
  • The trash/notification cleanup loop skipped its first run for up to an
    hour
    on every boot (it slept before its first tick), so short-lived
    containers could go a whole redeploy cycle without expiring trash or
    pruning old notification deliveries.

Performance

  • New model versioning is a single SELECT MAX() instead of loading
    every file row for the model.
  • The live-print DB sync skips its lookup query on repeat ticks — the
    active job is now cached per printer and re-queried only when the tracked
    file changes or the print finishes, instead of running a filtered query on
    every status update (several per second per printer).
  • Collection and model-detail permission checks are batched, replacing
    a per-row lookup with one query for the whole page — most noticeable in
    the sidebar on installs with many collections.
  • Filament/printer profile usage counts scan thinner rows, selecting only
    the matched columns instead of hydrating full metadata records for every
    live file.

Ops

  • CI now runs bandit and pip-audit on every PR, and a migration-upgrade
    job that seeds a database from the oldest supported release and replays
    every migration to head. Release tags are validated as strict semver before
    publishing.

Full changelog: v0.8.4...v0.8.5

v0.8.4 — integrity, security & performance hardening

Choose a tag to compare

@xiao-villamor xiao-villamor released this 09 Jul 15:58
dd194d9

Behavior changes on upgrade — read before installing.

Security

  • JWT secret can no longer boot with the shipped default. The app now generates and persists a real secret on first boot; if VAULT_JWT_SECRET was left at the placeholder value, existing sessions are invalidated once on the upgrade boot. Set your own with openssl rand -hex 32 if you manage it explicitly.
  • SQLite foreign keys are now enforced. The upgrade repairs any orphaned rows first (clears a dangling attribution, or drops a row whose required parent no longer exists) so enforcement doesn't turn a later delete into a crash.
  • Outbound fetches are pinned to the address the SSRF guard validated, closing a DNS-rebind gap between validation and connection.

Fixed

  • Printer history re-import could duplicate every past job. Comparing imported filenames case-sensitively (and, in one path, against only the first character of the stored name) meant a printer that changed filename casing between polls — or any poll at all — could re-import its whole history. Comparison is now a real case-insensitive match on both sides.
  • Backup restore could race the background jobs. Restoring while GC, an external library scan, or a live print sync were in flight could leave the database mid-swap under them. Restore now waits briefly for in-flight ingestion to finish and refuses with a 409 if it doesn't; GC and printer sync skip their tick while a restore is in progress.

Performance

  • Collection permission checks and model-list role resolution now run as a single SQL query instead of a per-row Python scan.
  • Print-job ingestion is atomic — a failed thumbnail no longer leaves a half-built model behind.
  • The printer WebSocket fan-out no longer blocks on a slow client. One stalled browser tab used to stall the update for every other subscriber to that printer; sends now run concurrently with a timeout, and a slow or dead socket is dropped instead of blocking the rest.
  • The dashboard's storage-usage figure is cached for 60 seconds instead of walking the filesystem on every load.
  • Print statistics no longer re-hydrate every completed job on every dashboard load. Cost and effective filament grams are now resolved once, when a print completes, and summed directly. Behavior change: a print's cost is frozen at completion time — editing a filament profile's price afterwards no longer revises the cost of prints that already finished.

Full changelog: v0.8.3...v0.8.4

v0.8.3 — data-loss hotfix: document files

Choose a tag to compare

@xiao-villamor xiao-villamor released this 09 Jul 14:36
a9d7368

Upgrade immediately if you use Documents.

Fixed

  • Hourly cleanup could delete document files. The background job that removes unreferenced files only recognised model files as "in use", so uploaded documents (PDFs and other binaries) were treated as leftovers and deleted. Only local storage was affected; S3/R2 installs were not.

    Documents deleted by an earlier version cannot be recovered by upgrading — restore them from a backup taken before the loss, if you have one.

  • Backups omitted document files. For the same reason, document blobs were never written into the backup archive, so restoring a backup silently lost them. New backups include them.

  • S3/R2 existence checks hid errors. A credentials or permissions failure was reported as "this file does not exist" instead of raising. Combined with the cleanup job, a transient auth error could have been read as "every file is unreferenced".

Under the hood

The cleanup sweep and the backup manifest each answered "which files does the database still own?" separately, and only one of them knew about documents. Both now share a single census (all_owned_blob_keys), so they cannot disagree again. Regression tests cover a document surviving a cleanup cycle, a trashed model keeping its bytes until it is purged, genuine leftovers still being collected, and a document round-tripping through backup and restore.

Full changelog: https://github.com/xiao-villamor/PrintStash/blob/main/CHANGELOG.md

v0.8.2

Choose a tag to compare

@xiao-villamor xiao-villamor released this 07 Jul 19:08

Added

  • Grid drag-to-move. Drag a model card onto a folder in the grid view to move it there.
  • Trademark policy. Added TRADEMARKS.md to protect the PrintStash name and branding; the project stays licensed under AGPLv3.

Fixed

  • Remember-me login. Fixed the remember-me checkbox not persisting the session correctly, and shortened the token lifetime for accounts that don't opt in.
  • Query client. Dropped a duplicate collections invalidation that could cause extra refetches.
  • Docker Compose (light). Repaired docker-compose.light.yml.

v0.8.1 — Printer controls & mobile polish

Choose a tag to compare

@xiao-villamor xiao-villamor released this 29 Jun 12:45

Added

  • Live printer controls (Moonraker). The printer Status tab now lets you set the hotend and bed target temperature, apply one-tap PLA/PETG/ABS preheat presets or Cooldown, Home all axes, and trigger an Emergency stop. Controls are gated behind a new `can_send_gcode` capability, so they appear only for providers that accept G-code (hidden for Bambu LAN).

Fixed

  • Mobile scrolling. Pages use the dynamic viewport height (`dvh`) so they scroll fully under the mobile browser chrome; the model detail page scrolls when its right-hand panel overflows; and the vault, model, and document pages keep their last content clear of the fixed bottom navigation bar.
  • Thumbnails. Brighter, higher-contrast mesh lighting on the dark theme.

Full changelog: v0.8.0...v0.8.1

v0.8.0

Choose a tag to compare

@xiao-villamor xiao-villamor released this 29 Jun 08:38
176cad0

Added

  • Spoolman integration (optional, OFF by default). Connect a self-hosted
    Spoolman instance under
    Settings → Spoolman with a base URL and optional API key, behind a master
    switch. A Test connection action and a live status badge confirm reachability,
    and the connection is reported in /health (informational — a Spoolman outage
    never marks the service degraded or blocks a print).
  • Spool inventory display. The Spoolman card lists spools with their
    remaining weight, pulled live from Spoolman (the source of truth).
  • Filament preset sync (Spoolman → PrintStash, one-way). A "Sync from
    Spoolman" action on the Profiles page imports Spoolman filaments as
    FilamentProfile presets keyed by spoolman_filament_id (deriving $/kg from
    price / weight, plus material, brand, density, and diameter). Synced presets
    are read-only in PrintStash (edit them in Spoolman); the first sync adopts
    matching local presets by name+material instead of duplicating, and filaments
    removed in Spoolman are unlinked (reverted to editable local), never deleted.
    Sync also runs automatically when the integration is enabled. This keeps
    filament data in one source of truth instead of drifting across two apps.
  • Exact per-print cost & weight from the selected spool. When a print used a
    synced spool, its cost comes from that filament's real Spoolman price and its
    grams use the spool's real density/diameter (mm_to_grams override) instead of
    the static per-material table — replacing fuzzy metadata matching for those
    prints.
  • Per-print spool selection. Choose which spool a print consumes when
    sending a job to a printer (send-to-printer) or logging a print manually.
    The spool is persisted on the print record (PrintJob.spool_id/spool_name)
    and shown in the model's print history.
  • Consumption write-back. On a Moonraker-measured print completion,
    PrintStash decrements the selected Spoolman spool by the measured
    filament_used_g (server-side via Spoolman's /spool/{id}/use), reusing the
    existing print_results + mm_to_grams pipeline. Once per job, after the job
    is committed, so it never blocks the print path. Bambu reports no live
    consumption, so its prints are skipped.
  • Double-count safety. Before writing consumption back, PrintStash checks
    Spoolman's active spool at completion time; if Moonraker's own Spoolman
    integration is already decrementing it, PrintStash skips its own write so a
    print is never counted twice — even if you never opened the settings card.
    The UI also warns when this is detected. An operator who has disabled
    Moonraker's hook can set a "write back anyway" override to make PrintStash
    own the consumption.
  • Collection documents & READMEs. Attach documentation to any collection:
    write Markdown in an in-app editor (live preview, paste-or-drop image embeds)
    or upload PDFs and other files. New Markdown docs open straight in the editor
    and aren't created until you save. PDFs render inline in a themed pdf.js viewer
    (page navigation, zoom, fit-to-width) that matches the app instead of the
    browser's default chrome. Each collection can also carry a README shown at the
    top of its page. Access follows the collection's role (view / edit / admin).

Changed

  • Mobile UI refresh. Rebuilt the bottom navigation as a five-slot
    Material-style bar (four destination tabs plus an always-present "More"
    sheet) instead of cramming up to seven items across the width — readable
    labels, a clear active indicator, and the account actions (username +
    Log out) moved into the More sheet.
  • Search on mobile. The top-bar search is now available on small screens;
    the redundant user-avatar menu is hidden there since navigation and account
    actions live in the bottom nav.
  • Library header on mobile. The "All Models" header stacks its title and
    actions on narrow screens (so the heading no longer wraps), with tighter
    padding and smaller folder cards.

Fixed

  • Dark-mode Catalog stat cards no longer show bright/stray dividers — the
    Collections/Tags summary now uses theme-aware hairline separators.
  • The internal __external__ sentinel model (placeholder for external print
    jobs) no longer leaks into the "All Models" grid — the library browse now
    excludes it, matching the header count that already did.
  • Batch tag/move no longer creates a tag or destination collection as a side
    effect when every selected model fails the permission check; taxonomy is only
    created once at least one model qualifies. Batch permission checks were also
    collapsed from a per-model grants query into a single pass.
  • Upload drag-and-drop is steadier: the drop highlight no longer flickers when
    the cursor crosses items inside the dropzone, the copy cursor shows while
    dragging, and a folder that fails to read now surfaces an error instead of
    silently doing nothing.
  • Spoolman Test connection now probes the values typed into the form, so you
    can verify a connection before saving — it no longer tests only the last-saved
    config. Save and Test now show clear success/error feedback (a "Saved."
    / "Connected — Spoolman vX" line and an immediate status-badge update) instead
    of appearing to do nothing, and a failed connection names the error instead of
    showing a bare "transport error:".
  • The PrintStash logo and the document Back link now return to the Documents
    tab when that's where you were, rather than always resetting to Models.

Internal

  • New app.services.spoolman (SpoolmanClient over the shared httpx pool, plus
    a blocking use_spool_weight_sync for the worker-thread finish tick),
    app.api.v1.spoolman router (superuser, secret-masked config), a
    record_spool_usage helper in print_results, a _spoolman_probe health
    component, and the spoolman_* switches on SystemConfig with an Alembic
    migration. Covered by client/helper/API unit tests and a frontend API-contract
    test; e2e mock routes added for the Spoolman endpoints.
  • Consumption write-back gained a write-time double-count guard: a blocking
    active_spool_sync probe in app.services.spoolman and a
    spoolman_write_force override on SystemConfig (third Alembic migration).
    record_spool_usage skips the decrement when Spoolman reports an active spool
    unless the override is set. Covered by unit tests.
  • app.services.filament_sync (Spoolman→preset reconcile), spoolman_filament_id
    /density_g_cm3/diameter_mm on FilamentProfile and spool_filament_id on
    PrintJob (second Alembic migration), a density override on mm_to_grams,
    and model_views.filament_cost_for_job for spool-exact cost. Read-only
    enforcement on linked presets in the filaments API.
  • Collection documents: app.api.v1.documents (CRUD, file upload, Markdown image
    embeds) with a Document model and a collection readme field across two
    Alembic migrations. Frontend adds react-pdf/pdfjs-dist (lazy-loaded, code
    split) for the inline PDF viewer; the nginx config now serves .mjs as
    application/javascript so the pdf.js module worker loads (browsers reject a
    module worker served as application/octet-stream). POST /spoolman/test
    gained optional base_url/api_key overrides so the UI can probe unsaved
    values. Covered by document/README and Spoolman API tests.