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
FilamentProfilepresets keyed byspoolman_filament_id(deriving$/kgfrom
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_gramsoverride) 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
existingprint_results+mm_to_gramspipeline. 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(SpoolmanClientover the shared httpx pool, plus
a blockinguse_spool_weight_syncfor the worker-thread finish tick),
app.api.v1.spoolmanrouter (superuser, secret-masked config), a
record_spool_usagehelper inprint_results, a_spoolman_probehealth
component, and thespoolman_*switches onSystemConfigwith 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_syncprobe inapp.services.spoolmanand a
spoolman_write_forceoverride onSystemConfig(third Alembic migration).
record_spool_usageskips 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_mmonFilamentProfileandspool_filament_idon
PrintJob(second Alembic migration), adensityoverride onmm_to_grams,
andmodel_views.filament_cost_for_jobfor 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 aDocumentmodel and a collectionreadmefield across two
Alembic migrations. Frontend addsreact-pdf/pdfjs-dist(lazy-loaded, code
split) for the inline PDF viewer; the nginx config now serves.mjsas
application/javascriptso the pdf.js module worker loads (browsers reject a
module worker served asapplication/octet-stream).POST /spoolman/test
gained optionalbase_url/api_keyoverrides so the UI can probe unsaved
values. Covered by document/README and Spoolman API tests.