All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
1.7.0 - 2026-09-04
- Settings page has a "Reload database" button that re-reads the data file from disk into the running server, so a restored backup or manually-edited
spoolman.jsontakes effect without a restart. Invalid or unreadable files are rejected and the currently-loaded data is kept.
1.6.1 - 2026-09-03
- Spool create/edit color picker now has a
#rrggbbtext field next to the swatch for typing or pasting an exact hex value. - Spool and filament list filters (text search, material, location, colour level, colour pick, show-archived) now persist for the browser tab session, so they survive opening a detail page or reloading the tab. They reset when the tab is closed.
- "Clear filters" button on the spool and filament list headers, shown only when a filter is active, resets every filter at once.
- Spool edit form now preselects the spool's current storage location in the Location drop-down instead of showing "— none —".
- Removed the black square marker from the spool list "Material" column header when a material filter is active; the drop-down already shows the selected value.
1.6.0 - 2026-09-01
- Spool list text search now also matches storage location name, in addition to filament name, colour name, and material.
- Spool edit form now shows today's date in empty
First used/Last usedpickers instead of leaving them blank. An untouched default is not saved, so the fields stay unset unless the user picks a date. - New spool form: a SpoolmanDB search that returns no match no longer disables the filament selector when the query matches a filament you already have. The matching local filament is selected automatically.
- Spool creation now requires a valid
location_id: the API rejects a spool with no location, or a location that does not exist, with HTTP 422. Spool updates that setlocation_idare validated against existing locations. - Filament edit page heading now reads "Edit " instead of a generic "Edit Filament".
- Spool list: clicking the filament name in the first column now opens the spool detail page instead of the filament detail page.
- Filament detail page heading is prefixed with "Filament:" so it is distinguishable from a spool detail page at a glance.
1.5.0 - 2026-04-12
- Spool list sorts by closest color match (ascending ΔE*00) when a color search level is active. Switching back to "Off" restores the default column sort order.
- "Color" column header in the spool list is now clickable — clicking it (or pressing Enter/Space when focused) activates the color picker filter in the page header. The header shows a pointer cursor and accent hover style to signal interactivity.
- Playwright test suite (
tests/playwright/) covering navigation, filament list/detail/create/edit, spool list/detail/create/edit/clone, and API schema validation (38 tests, all passing). - Light theme colour palette derived from the Spoolman Light logo: cyan accent (
#4DC8E8), charcoal text (#3D4555), and off-white sidebar background (#F0F2F5) replace the previous generic greys and blue. Dark mode tokens are unchanged. - CSS styling via
stylers 0.3.2— every Leptos component now has a scopedstyle!block. Global styles (CSS custom properties, dark-mode overrides, reset, buttons, shared page classes) live instyle/spoolman.cssbundled viaLeptos.tomlstyle-file. The app is no longer completely unstyled (fixes B4). - Rust integration test suite — 24 tests covering health, filament, spool, location, and settings endpoints via in-process Axum dispatch; runs with
cargo test -p spoolman-server, no Docker required.
remaining_pctfield dropped fromSpoolResponseAPI and all UI surfaces (spool list column, spool detail row). The value was redundant — derivable asremaining_filament / net_weight × 100— and only meaningful whennet_weightis set, causing confusing "unknown" states otherwise.
- Docker build no longer fails when GitHub Releases is unreachable:
wasm-bindgen-cliis now pre-installed viacargo installin the builder stage socargo-leptosfinds it on$PATHinstead of downloading a pre-built binary at build time. scripts/run-e2e.shnowcds intotests/e2e/before running Playwright soplaywright.config.ts(and itsbaseURL) is picked up correctly. Previously all 15 E2E tests failed with "Cannot navigate to invalid URL" because Playwright ran from the repo root without a config.- Spool detail view now shows
first_usedalongsideregisteredandlast_used, with dates formatted as human-readableYYYY-MM-DD HH:MM UTCinstead of raw RFC 3339. The edit form gainsFirst usedandLast useddatetime-local inputs that round-trip dates through theUpdateSpoolAPI (fixes B14). - B15 Delete buttons now show inline "Sure?" / "Cancel" confirmation before removing any entity.
- B15 Location list rows disappear immediately after delete (replaced
refetch()with a version-counter reactive source). - B15 Spool and Filament list pages now have Delete buttons per row.
- B15 FilamentShow now has a Delete button.
- B15 Navigating to a deleted spool or filament (HTTP 404) redirects to the list view instead of showing an error.
- Spool create and edit dialogs now reject the form with an error message when no location is selected — previously the form submitted with
location_id: Nonesilently (fixes B16). - Help page had three issues: the "Data file" section linked to
/api/v1/setting(returns an empty map) with misleading text; the NFC section displayed the literal text<id>instead of<id>due to double-escaping in the Leptos text node; and no/infoendpoint existed. AddedGET /api/v1/info(returns{ version, data_file }), updated the link and label to point to/api/v1/info, and corrected the NFC URL string (fixes B17). /api/v1/inforeturned empty — the client was fetching/infobut the route is mounted at/api/v1/info; corrected the client path.- Opacity (alpha) of spool colours was hardcoded to 255 — the native
<input type="color">element has no alpha channel. Added an<input type="range">opacity slider (0–255) next to the colour picker in both the create and edit spool forms. The current percentage is shown as a label; the edit form pre-fills the slider from the saved colour's alpha value (fixes B7). - Spool create and edit both returned HTTP 500 when the selected filament had
net_weight = Some(0.0)— division by zero inSpoolResponse::newproducedNaN/±infforremaining_pct, whichserde_jsoncannot serialize, causing Axum to return 500 instead of the expected 201/200. Added.filter(|&nw| nw > 0.0)before the.map()soremaining_pctisNonewhennet_weightis zero or absent. Regression testcreate_spool_with_zero_net_weight_filament_returns_201added (fixes B8, B12). Also fixed spool edit navigation: after a successful save the client now navigates to/spools(list) instead of/spools/{id}(detail) (fixes B9). - Filament list sorting was completely non-functional — same root cause as spool list (sort signals tracked for display only, never applied to data). Added
sortedclosure with numeric comparison for density and date comparison for registered. AddedColHeaderfor the Density column (was a plain<th>). Manufacturer and Material sort by case-insensitive string;Nonevalues sort last regardless of direction (fixes B11). - Spool list sorting was completely non-functional —
sort_field/sort_ascstate was tracked for display but never applied to the data. Added asortedclosure that sorts the filtered items before pagination using numeric comparison for IDs, weights, and percentages (not lexicographic), and string comparison for filament names.Remaining (g)andLocationcolumns previously had no sort button (<th>with noColHeader); replaced withColHeaderwired toremaining_weightandlocationfields respectively.Nonevalues always sort last regardless of direction (fixes B6, B10). - Locations Delete button rendered raw Leptos macro source as its label (
0 on : click = move | _ | on_delete(id) >Delete) —>indisabled=move || count > 0was parsed as the closing>of the<buttontag; extracted tolet delete_disabled = count > 0before theview!block (same fix pattern as Pagination "Next →"). Also added.btn:disabledCSS rule so disabled buttons keep their color identity rather than looking like unstyled pagination buttons (fixes B5). - Pagination Next/Prev buttons were always disabled — the previous fix for the
>=parsing issue extractednext_disabledas aSignal::derive(…), but Leptos 0.6 does not create reactive attribute bindings for a bareSignal<bool>passed by name: it reads the initial value (alwaystruebefore data loads) and never updates. Changednext_disabledto a plainmove || …closure so the attribute binding is reactive (fixes B13). - Spool create form sent
filament_id: 0causing a 404 — added acreate_effectthat initializes the signal to the first loaded filament's ID when the resource resolves. - Spool create and edit forms always sent an empty
colorsarray — added<input type="color">picker wired throughhex_to_rgba; edit form pre-fills the picker from the spool's existing first color.
1.4.0 - 2026-03-24
- Python/FastAPI backend (
spoolman/) — the Rust/Axum server is now the sole backend. No API changes for consumers. - Docker-based Python integration test suite (
tests_integration/) — no longer applicable to the Rust stack. Rust integration tests are tracked as a follow-up. entrypoint.sh— the uvicorn launcher is not needed; the Rust binary bindsSPOOLMAN_HOST/SPOOLMAN_PORTdirectly.scripts/install.shandscripts/start.sh— Python/venv install helpers; superseded bydocker compose uporcargo leptos build.pyproject.toml,pdm.lock,uv.lock— Python package management files.- CI jobs
style(Python pre-commit) andbuild-client(Node/pdm) replaced bycargo checkandcargo clippyjobs.
PUID/PGIDenv vars no longer supported — the Docker image runs as a fixedappuser (uid 1000). Ensure volume mounts are accessible by uid 1000.
1.3.0 - 2026-03-24
- React/TypeScript frontend (
client/) — the Leptos WASM frontend (crates/spoolman-client) is now the sole UI. Theclient/directory, npm tooling, and all Node.js build configuration have been deleted.
1.2.0 - 2026-03-24
- Color proximity filter on spool list: a color picker and threshold slider let users find spools by color similarity using CIEDE2000 (ΔE*00) — a perceptually uniform metric that matches human color vision. Default threshold is 10 ΔE (≈ "acceptably similar"). Replaces the earlier Euclidean RGB distance.
1.1.0 - 2026-03-22
MaterialTypeenum inspoolman-typesbased on the OpenPrintTagmaterial_type_enumspec (42 named variants +Other(String)catch-all). Serializes as uppercase abbreviation (e.g."PLA"); unknown strings round-trip without error.- Material
<select>on filament create/edit forms — replaces free-text input with a dropdown of all 42 spec-defined types plus a "select" blank option. - Material filter dropdown on the Filament list page; filters are applied server-side via
?material=query param. - Spool list text filter now also matches on
filament.materialabbreviation (e.g. typing "PLA" narrows spool results). GET /api/v1/materialclient wrapper (api::list_materials) for future datalist/autocomplete use..envfile support viadotenvy: the server silently loads a.envfile from the working directory on startup, before reading environment variables. Missing file is not an error.
1.0.0 - 2026-03-23
- Full Rust rewrite: Axum backend + Leptos WASM frontend in a Cargo workspace (
crates/spoolman-types,crates/spoolman-server,crates/spoolman-client). Build withcargo leptos build --release. docker-compose.ymlat repo root for quick local deployment; data persisted in a named Docker volume at/data/spoolman.json.rust-toolchain.tomldeclares the stable toolchain withwasm32-unknown-unknowntarget sorustupinstalls it automatically.spoolman-typesshared crate:Spool,Filament,Location,DataStoretypes used by both server and client, ensuring compile-time API contract consistency.Locationas a first-class entity with full CRUD (GET/POST/PATCH/DELETE /api/v1/location) — replaces the previous freeform string field on Spool.GET /api/v1/filament/search?q=endpoint: proxies SpoolmanDB on demand (no background scheduler or local cache).GET /api/v1/exportendpoint: downloads the full data store as JSON (useful for backup and migration).- Dark mode toggle with CSS variable switching, persisted in localStorage.
- Spool clone action (
POST /api/v1/spool/<id>/clone). SPOOLMAN_DATA_FILEenvironment variable to configure the path of the JSON data file (default:~/.local/share/spoolman/spoolman.json).
- BREAKING: Entire stack replaced — Python/FastAPI backend and React/Refine/Ant Design frontend superseded by a Rust Cargo workspace (Axum + Leptos WASM). Docker image no longer requires Python runtime or Node.js build artifacts.
- BREAKING: JSON storage format redesigned for Rust/serde ergonomics. No existing data to migrate (format was unconstrained).
- BREAKING: Spool and Filament IDs are now random
u32values (previously sequential integers), stable across export/reimport for NFC tag URL durability. - BREAKING: Colors represented as
Vec<RGBA>(OpenTag3D/OpenPrintTag compatible) instead of hex strings. Color lives on Spool, not Filament. - BREAKING: Weight tracked as
initial_weight+current_weight(full scale readings). Three-mode weight entry (used/remaining/measured) removed. - BREAKING: The
Vendorentity removed; vendor is a plain string on Filament. - BREAKING:
article_number,external_id,lot_nr,extrafields removed from all entities. - BREAKING: WebSocket live-update endpoint removed.
- BREAKING: Backup download endpoint removed (backup still runs automatically in background).
- Spool NFC Online Data URL maps to
/api/v1/spool/<id>(OpenTag3D-compatible). - Dockerfile:
SPOOLMAN_DATA_FILEdefaults to/data/spoolman.jsonandLEPTOS_SITE_ROOTdefaults to/sitein the container, matching the volume mount convention. - Static asset path in the server is now read from
LEPTOS_SITE_ROOTenv var (fallback:target/site), enabling the production Docker layout without recompilation. - Integration test suite (
tests_integration/) rewritten for the Rust API: new Filament/Spool/Location models, RGBA color schema,current_weightweight tracking, new Location CRUD tests, settings tests updated forPUT /api/v1/setting/:key,fields/tests deleted,test_use.pyandtest_find_by_color.pydeleted,test_measure.pyrewritten ascurrent_weightPATCH tests,test_backup.pyreplaced with export test.
entrypoint.shhad Windows-style CRLF line endings, causing the Docker container to fail to start on Linux with "no such file or directory".JsonStore._flushcould raiseFileNotFoundErrorunder concurrent requests due to multiple threads racing to rename the same.tmpfile; writes are now serialized with a reentrant lock.- Filtering spools by empty
filament.name,filament.material, orfilament.vendornow correctly returns spools whose filament has no value set for that field.
Vendorentity and all/api/v1/vendorendpoints.color_hex,multi_color_hexes,multi_color_directionfrom Filament (moved to Spool ascolors: Vec<RGBA>).price,weight,spool_weight,article_number,external_idfrom Filament.lot_nr,external_idfrom Spool./api/v1/spool/find-by-colorendpoint (no replacement; color filter is a planned enhancement)./api/v1/field/*extra-fields system.- Database backends: SQLite, PostgreSQL, MySQL, CockroachDB.
- Python dependencies:
SQLAlchemy,alembic,aiosqlite,asyncpg,aiomysql,psycopg2-binary,sqlalchemy-cockroachdb. - Alembic migration directory (
migrations/) andalembic.ini. - Prometheus metrics endpoint (
GET /metrics) and theSPOOLMAN_METRICS_ENABLEDenvironment variable. - WebSocket support on all REST endpoints — use polling instead.