Commit Graph
282 Commits
Author SHA1 Message Date
Donkie a039095360 Replace Ko-fi buttons with GitHub Sponsors
Both frontends and the README now point at github.com/sponsors/Donkie.

The GitHub-suggested iframe embed is not usable here: Spoolman is often
run on a LAN with no internet access, so it would render as an empty box,
and it cannot be translated or themed. The existing buttons keep their
shell and get an inlined Octicon heart plus a translated label instead.

Only the en locale source is touched; the new "sponsor" key goes out to
the other locales via Weblate.
2026-08-23 17:38:31 +02:00
Donkie ba891d5830 client_v2: let a scanned tag keep the view you were in
Auto-navigate rewrote the URL to a bare `/?sel=spool:N`, which since the filter
menu landed means a tap throws away the grouping, sort and filter chips you had
set up -- and lands on the shipped default view rather than the remembered one.

openSearchResult is the navigation a picked search result already gets, and a
tag scan asks the same question: on the Library it merges the selection into
the current view, elsewhere it opens the Library on just that spool. The
inspector resolves a selection by id on its own, so the spool still opens when
the active filters exclude it from the list behind it.
2026-08-23 10:07:02 +02:00
Donkie 931e3c9b3b Merge remote-tracking branch 'origin/master' into worktree-nfc-design-docs
Master split filter application in two: the filters a filament can answer on
its own (_apply_filament_filters) now serve both the spool query and the new
include_empty query, which selects FROM the filaments so that a filament with
no spools still produces a group.

The tag filter stays where it was, in _apply_spool_filters, and the comment
there now says why it cannot move: a tag is linked to a spool, so a filament
on its own can no more answer "which tag is on you" than it can answer "which
shelf are you on". Nothing routes a tag filter into the filament-driven query
-- it never calls this builder, and the group endpoint takes no `tag` -- so
there is no silently-dropped filter to guard against.
2026-08-23 10:06:30 +02:00
Donkie 9775c2c42e Merge pull request #1097 from Donkie/client-v2-issue-1080-rest
client_v2: sort direction, out-of-stock filaments and truncation tooltips
2026-08-23 09:04:47 +02:00
Micha 91d5dc8f42 Translated using Weblate (German)
Currently translated at 70.9% (384 of 541 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/de/
2026-08-22 19:04:36 +02:00
cedrik-s 8528847957 Translated using Weblate (German)
Currently translated at 70.9% (384 of 541 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/de/
2026-08-22 19:04:36 +02:00
cedrik-s 5799fac1a6 Translated using Weblate (German)
Currently translated at 70.9% (384 of 541 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/de/
2026-08-22 19:04:36 +02:00
cedrik-s ce1c781ae7 Translated using Weblate (German)
Currently translated at 70.9% (384 of 541 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/de/
2026-08-22 19:04:36 +02:00
cedrik-s dc72f3d5eb Translated using Weblate (German)
Currently translated at 70.9% (384 of 541 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/de/
2026-08-22 19:04:36 +02:00
cedrik-s aa5821f005 Translated using Weblate (German)
Currently translated at 70.9% (384 of 541 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/de/
2026-08-22 19:04:36 +02:00
cedrik-s 13f7667bf4 Translated using Weblate (German)
Currently translated at 70.9% (384 of 541 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/de/
2026-08-22 19:04:35 +02:00
cedrik-s ed732170d1 Translated using Weblate (German)
Currently translated at 70.9% (384 of 541 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/de/
2026-08-22 19:04:35 +02:00
Micha 8465481ab3 Translated using Weblate (German)
Currently translated at 70.9% (384 of 541 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/de/
2026-08-22 19:04:35 +02:00
cedrik-s 8b71fb9345 Translated using Weblate (German)
Currently translated at 57.8% (313 of 541 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/de/
2026-08-21 21:51:23 +02:00
Micha 9bb8973af2 Translated using Weblate (German)
Currently translated at 57.8% (313 of 541 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/de/
2026-08-21 21:51:22 +02:00
Jeff PapiezandCopilot App 97758f2c4c Let prettier respect the working tree's line endings
The root .gitattributes sets `* text=auto`, so a Windows checkout gets CRLF in
the working tree. Prettier defaults to `endOfLine: "lf"` and therefore reports
every text file as mis-formatted -- 163 files in client_v2 alone -- which makes
`npm run lint` and `lefthook run ci` unusable locally. The pre-commit hook is
worse: it runs `prettier --write .` with stage_fixed, so any commit made from
Windows rewrites and stages the whole client tree.

Set `endOfLine: "auto"` in both client configs. Git still normalizes to LF in
the index and in the repo thanks to `text=auto`, and CI runs on ubuntu-latest,
so LF in version control is unaffected; prettier just stops fighting the
checkout. This is Prettier's documented fix for this scenario.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 21:50:44 -07:00
Donkie 6ab045cde8 Merge remote-tracking branch 'origin/master' into client-v2-issue-1080-rest
# Conflicts:
#	client_v2/src/lib/components/library/ListToolbar.svelte
2026-08-19 20:38:38 +02:00
Donkie 90492ff75d client_v2: make the no-spools filaments an opt-in view
Listing every filament you own no spools of turned out to be the wrong default.
Finding a spool to print with is the daily job and those rows cannot serve it,
and how many of them there are scales with the size of a user's catalogue -- a
no-op for one library and a tripling of another. Off by default, one click to
turn on, remembered afterwards: a missed feature beats a primary view that is
permanently worse until you find the switch.

The toggle sits beside Show Archived and shows a dismissible chip when on, and
is offered only while grouping by filament, since there is nowhere else to put
the empty groups.

Remembered through viewPrefs, which until now deliberately stored nothing that
hides rows -- a hidden filter restored days later reads as missing data. This
one only ever adds rows, so it cannot cause that confusion; the comment there
now says which side of the line it falls on and why. Entries written before the
flag existed keep working: a missing field means off rather than a corrupt entry
that would throw away the grouping the user picked.

The badge now reads "No spools" rather than "Out of stock". Spoolman knows what
you have recorded, not what is on your shelf.
2026-08-19 17:20:26 +02:00
Julia 96065fbd9f Translated using Weblate (Polish)
Currently translated at 100.0% (541 of 541 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/pl/
2026-08-19 11:34:54 +02:00
Icezaza Ch bd877c1ec7 Translated using Weblate (Thai)
Currently translated at 100.0% (541 of 541 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/th/
2026-08-18 19:51:40 +02:00
Liudas Ališauskas 328c508c39 Translated using Weblate (Lithuanian)
Currently translated at 34.7% (188 of 541 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/lt/
2026-08-18 19:51:30 +02:00
Micha 052d3e9eb9 Translated using Weblate (German)
Currently translated at 51.0% (276 of 541 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/de/
2026-08-18 19:51:25 +02:00
cedrik-s 9a98e8ba5d Translated using Weblate (German)
Currently translated at 51.0% (276 of 541 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/de/
2026-08-18 19:51:24 +02:00
Donkie 6dce8717a0 Fold the empty-group query back into find_groups
The first cut of include_empty grew a second 177-line function next to
find_groups that repeated its filters, ordering, pagination and hydration, so
every future change to grouping had to be made twice or silently diverge.

Only the FROM ever differed. Writing the aggregates so they hold for a group
with no spools in it -- COUNT over a column instead of COUNT(*), one explicit
zero for the one SUM -- removes the need for a second set, and with it the
COALESCE that CockroachDB rejected and the CAST that worked around it. What is
left is one query with a branch over how its rows are selected, and one shared
tail.

Spool-level filters are now refused alongside include_empty rather than quietly
producing a contradiction (every filament as empty, or no empty groups at all).
That also leaves the archived rule as the only spool-level condition the
filament-driven query has to carry, in its JOIN.

Group ordering now breaks ties on the grouped column. Every ordering can tie,
and empty groups tie on all four aggregates, so paging could otherwise repeat or
drop a group between pages.
2026-08-18 18:04:34 +02:00
Donkie 33ad8d7173 Show filaments you have no spools of
Grouping by filament aggregates over spools, so a filament you own no spools of
could never produce a group and simply vanished from the library. A filtered
view then looked fully stocked when it was the opposite, and the thing you
needed to re-order was the one thing not on screen (#1092).

GET /api/v1/spool/group takes include_empty, which inverts the query: spools are
aggregated per filament in a subquery that the filament list is LEFT JOINed
against, so filaments with no matching spools survive with their aggregates
folded to zero. Which side of that join a filter goes is the whole subtlety, and
the docstring spells it out - spool-scoped conditions have to stay in the
subquery or they delete the very rows the flag exists to keep.

The client asks for it while grouping by filament and every active filter is one
a filament can answer on its own. "Which of these are on Shelf A" has no answer
for a filament that is nowhere, and including them there would bury the shelf
under the whole catalogue. Such a group renders as an out-of-stock row: no
disclosure, no fetch, and a marker in place of the zeroes it would otherwise
report.

The group header also picks up the truncation tooltip added in the previous
commit. Its title sits under a link stretched across the whole row, so the
pointer never lands on the text itself and the tooltip has to hang off the row.
2026-08-18 17:33:29 +02:00
Donkie 713cb51f09 client_v2: reveal truncated text on hover
Text cut off with an ellipsis gave no way to read the value behind it, and that
value — a filament name, a location, a manufacturer — is usually the whole
reason the row is on screen (#1093).

A shared action measures the element and gives it a title only while the text
really is cut off, so rows that fit stay quiet instead of popping a redundant
tooltip under every pass of the pointer. It re-measures on resize and on text
changes alike: the list pane is resizable, and a live update can lengthen a name
without the column moving at all.

Applied to the spool rows, the unused-spool rows, the search results and the
dashboard chips.
2026-08-18 17:32:01 +02:00
Donkie fa39c6d08e client_v2: flip the sort direction from the chip's own arrow
The sort chip showed the direction but opened the menu wherever you clicked it,
so reversing an order meant reopening the dropdown and finding the field that
was already selected (#1091).

Split it into two controls sharing one frame: the field label still opens the
menu, the arrow only flips the direction. A divider and a hover of its own mark
the arrow as separate, and both halves stretch to fill the chip so the 44px
mobile tap target survives the split.
2026-08-18 17:31:38 +02:00
Donkie 61e2c0c27c client_v2: keep the frontend suite in step with the filter menu
Turning filter values into checkboxes that leave the menu open broke the
`addFilter` helper in two ways, and with it the grouping test that uses it
to pin the list down to its own spools.

An explicit `role="menuitemcheckbox"` replaces a button's implicit role, so
the helper's `getByRole("button")` no longer matched a value row at all;
and having picked one, the helper handed back a toolbar with the menu still
covering it, which the next call could not open through. It now asks for
the role the row actually has, and closes the menu itself, restoring the
postcondition every caller was already relying on.

The row's accessible name needed a real space rather than the margin that
had been standing in for one: without it a filament read as one
run-together word, "Galaxy BlackPLA".
2026-08-18 17:29:07 +02:00
Donkie 5624a0a605 client_v2: pick several filter values without reopening the menu
Narrowing a library to three filaments meant opening the filter menu,
stepping into Filament, picking one, and doing all of it again twice
more. The menu closed on every pick, and nothing in it said which values
were already on — the chips that say so sit in the toolbar, behind the
menu that covers them.

A value row now carries a checkbox and its state, the same way the "Show
archived" row already does, and picking one leaves the menu open. The two
go together: once the values are marked, the list reads as a multi-select,
and closing after a single pick is the surprising half. Escape and a click
outside still close it, and picking a filter property or a date range still
does too, since neither is a set.

Nothing has to be preserved by hand for this to work. Each toggle is a
query-only navigation that keeps the component mounted, and it already
asked to keep focus and not to scroll, so the search box stays focused and
the list stays where it was.

The Ctrl-modifier the issue suggested is deliberately not implemented: a
checkbox list that stays open needs no hint text to explain itself.

Closes #1090
Closes #1089
2026-08-17 21:00:17 +02:00
Donkie 6e289d76ca client_v2: show colors and material in the filament filter
The filament filter listed nothing but a label, which is the manufacturer
and the name. That is not enough to pick from: the colour is the thing
you actually recognise a filament by, and two filaments may legitimately
carry the same name in different materials, leaving the menu offering two
entries that read identically.

Each option now carries whatever it has beyond its label, so the menu
draws a swatch and the material where they exist and stays a plain list
of names where they don't (locations, lot numbers). The material sits
with the label rather than off at the row's right edge, since it is there
to qualify the name it follows. None of it costs a request: the option
list is built from whole filaments already fetched.

The search box narrows on the material too. It is on screen, so a query
that visibly matches a row has to keep it — and since terms match in any
order, "prusament petg" now finds one filament out of two same-named
ones.

Closes #1087
Closes #1088
2026-08-17 20:59:29 +02:00
Dr_Perry_Coke 2434b424db Translated using Weblate (Russian)
Currently translated at 100.0% (541 of 541 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/ru/
2026-08-16 18:48:22 +02:00
Donkie 38b77a9b14 Updated some strings 2026-08-15 10:55:36 +02:00
Donkie fb1567a9b0 client_v2: say what the scanner settings actually do
Nothing in this section sets a scanner up -- that is hardware, and the link
above it goes to the wiki for it. Every control here answers one question:
when a tag is scanned, does this screen jump to its spool, and which reader is
it listening to. "Tag scanner" as a heading promised the former and delivered
the latter.

So the section is "Scanned tags", the reader row talks about which scans open
spools here rather than about listening in the abstract, and "Listen to all"
becomes "Use any reader" -- the same thing said in terms of the outcome rather
than of the plumbing. The auto-navigate description drops its first sentence,
which only repeated the label it sits under, and spends the room on the limits
instead.

The e2e assertions that named this copy now match on the clause that
distinguishes the two states rather than on whole sentences, since prose gets
reworded and the assertion is about which state is showing.
2026-08-15 10:25:40 +02:00
Donkie 01c7ff65be client_v2: stop keeping a second copy of a reader's name
Pairing by tapping a tag showed "desk" where pairing with the same reader from
the recently-seen list showed "Desk reader". Not a display bug: the client was
persisting its own copy of the reader's name into localStorage at pairing time,
so there were two sources of truth for something the server owns and that
changes without us.

They drift by design. The server's registry keeps the last name each reader
gave (scanrelay.py:98, deliberately, so a trimmed-down agent doesn't blank a
label). A scan event carries only the name *that* scan sent. Pair from the list
and you read the registry's remembered name; pair by tapping a scan that
carried none and you stored null and fell back to the id. Same reader, two
answers.

Only the reader id is persisted now -- that is the durable half, and the only
thing pooling actually depends on. Names are read: merged from the registry and
from scans as they arrive, held in memory because they mirror state the server
also holds in memory, and falling back to the id when nobody knows a name,
which is the honest answer after either end restarts.

Verified against the reported scenario -- a reader that sent a name once and
then stopped -- pairing both ways and comparing the label.
2026-08-15 09:50:31 +02:00
Donkie ff2e3ec31a client_v2: keep scans off the pages you are configuring
Pairing a reader is done by tapping a tag on it, and that tap is delivered to
the auto-navigate subscription as well as to the settings page waiting for it.
So setting a scanner up navigated you away from the screen you were setting it
up on, the instant it worked -- and the same would happen to anyone mid-edit
when someone taps a tag in the next room.

Scans now only act on the Library and Dashboard: the pages where being moved to
a spool is what you wanted. Settings and the label designer are work in
progress, and neither navigates nor toasts. Matched on route id rather than
pathname, since an instance under SPOOLMAN_BASE_PATH serves the library from
somewhere that is not "/".

Also: the paired reader is now named in a chip beside the buttons instead of
inline in a sentence. Ids are routinely a bare "desk" or an "ip-192-168-1-50"
derived from the address, and "Listening to desk only." reads as a typo rather
than as the name of a device. The raw id stays on hover for when a friendly
name hides it.

The scanner section's explanatory paragraph is replaced by a link to the wiki.
How a reader reaches Spoolman needs wiring and firmware, which is an article,
not a caption.
2026-08-15 09:16:34 +02:00
Donkie 9ec5181d4c client_v2: link NFC tags to spools, and act on a reader's scans
The client half of design/nfc-phase-1-tag-identity.md, against the API that
landed in de533bc.

A spool's inspector grows a Tags section listing what is linked to it, with
adding as a quiet section action rather than another button in a header that
already carries the spool's primary actions. The dialog behind it takes a UID
from whichever source is to hand -- a reader tapping the tag, this phone over
Web NFC, or typing -- and resolves it as it settles, so "already on spool #12"
arrives before you commit rather than as a rejected submit. That case is
offered as one button that moves the tag, because the alternative is sending
the user to find the other spool and unlink it by hand.

The relay client is deliberately not part of live.ts. That module is one socket
per entity resource, each feeding the inventory cache, and a scan is not an
entity: it has no id, nothing to cache it under, and its URL carries a reader
id where live.ts's carries a resource name. The connection behaviour is worth
sharing, so the backoff, the wake-on-online/visible listeners, the keepalive
and the 401 probe are copied rather than generalized over both.

Which reader a browser listens to is settled by walking over and tapping a tag
on it: subscribe to every reader, take the id off the first scan, narrow to
that one. No codes, no server state, and the binding is self-evidently right
because the user made it with their hands.

Auto-navigate is off by default and subscribes only while it is on, so a
browser not using NFC holds no relay socket at all. Exactly one subscriber, in
the root layout, since two components reacting to one tap is how you get a
double navigation. It stands down while a dialog is open, while the tag dialog
holds it, and while a field is being typed in -- a tag tapped in the next room
must not take a half-typed value with it.

Web NFC is progressive enhancement: absent, not disabled, on desktop, iOS,
Firefox and every plain-HTTP install. A tag's serial is readable without
decoding its contents, so phase 1 needs no NDEF parsing.

Verified with six Playwright specs driving the relay over plain POSTs, which is
what the reader contract being ordinary HTTP buys -- the whole feature is
exercised without NFC hardware. Web NFC itself has no automated cover and
cannot; what is asserted is that its control is absent where it cannot work.
2026-08-15 08:38:34 +02:00
Donkie 91dec46936 client_v2: give toasts a neutral kind
Every message was either a green tick or a red warning, which leaves nowhere
to put the ones that are neither. A tag scanned that no spool claims is the
ordinary first step of enrolling one: reporting it as an error puts a warning
on a working flow, and as a success ticks a check at nothing having happened.

The neutral kind needs no styling of its own -- the base toast is already
neutral -- so it is an icon and a longer timer. The timer now follows from
whether a message is a confirmation rather than from whether it is an error:
a confirmation is read at a glance, an instruction has to be read properly.
2026-08-15 08:37:30 +02:00
KnightArch fd9b06d33c Translated using Weblate (Portuguese (Brazil))
Currently translated at 31.2% (169 of 541 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/pt_BR/
2026-08-14 08:46:28 +02:00
Alessandro “Alessandrol” L bf265c12c3 Translated using Weblate (French)
Currently translated at 25.4% (137 of 539 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/fr/
2026-08-13 18:22:34 +02:00
Donkie 9423acbc9e client_v2: create a filament while changing what a spool holds
The change-filament dialog could only pick a filament that already existed,
in the catalog or in SpoolmanDB. But the only way to create one anywhere in
client_v2 is the add-spools flow, which always mints at least one spool — so
refilling a shelf slot with a roll nobody had catalogued meant adding a
throwaway spool and deleting it again, handing out exactly the new spool id
the feature exists to avoid (issue #1010).

The dialog now offers the same third path the add-spools flow does. Picking
it swaps the search for the new-filament form and applies as one action:
the filament (and its manufacturer) are created, then the spool is repointed
at it. The spool keeps its number, location, dates and usage history.

The form itself moves to NewFilamentCards, shared with AddSpoolModal so both
dialogs keep the per-record card layout from #1038, with its rules and its
mapping onto the API in lib/filament/draft.ts. The one difference is the
weight/spool weight/price trio: the add-spools flow writes those to the spool
it creates as well, and so keeps showing them in its spool block, while here
they belong to the filament alone.
2026-08-13 18:11:56 +02:00
Donkie aac4a26cde Merge master into client-v2-form-required-fields
Master reorganized the spool half of the add form into headline / fill /
where / money groups and added the roll-size presets. Kept that layout and
re-applied the required-field wiring on top of it: the data-field hooks, the
per-field touch handlers and the err() calls that decide when an error shows.

FIELD_ORDER follows the new reading order too, so Fill level now comes before
Spool Weight when the form picks which bad field to jump to.
2026-08-12 22:20:39 +02:00
Donkie 2d08061c45 client_v2: require the material when creating a filament
Leaving Material blank sent you to Density instead. Density is only prefilled
for a material Spoolman recognises, so a blank material surfaced as "Density
is required" — an error on the field you didn't fill in because of the field
you did miss, and inside the collapsed Advanced block at that.

Material is now required on this form, so the error lands on the field that
actually needs attention and Advanced stays shut. Typing a material we don't
have specs for still asks for a density afterwards, which is the right order
to be asked in.

Client-side only: the API still accepts a filament with no material, so
existing ones are unaffected.
2026-08-12 20:25:00 +02:00
Donkie aec9b276b2 client_v2: point at the first bad field instead of listing them all
The summary panel above the add button was more machinery than the problem
needed, and it spent five translation strings restating what the inline
field errors already say. Drop it.

Pressing Add still reveals every outstanding error and still opens the
collapsed Advanced block when the problem is hiding in there; it just takes
you to the first bad field and lets the error under it do the talking. The
issue count on the Advanced toggle and the "* required" legend go too.

The change now adds no new translation strings at all.
2026-08-12 20:12:36 +02:00
Donkie 0931b03a80 client_v2: say what is missing instead of disabling the add button
An incomplete add-spool form was answered with a dead Add button, leaving
"which field haven't I filled in?" to be found by eye. Worst case the answer
was invisible: density is required but only prefilled for known materials,
and it lives inside the collapsed Advanced block, so the form could look
complete and still refuse.

Add stays clickable. Pressing it on an incomplete form reveals every
outstanding error at once, lists them above the button as rows that jump to
their field, opens the section hiding one, and puts the caret in the first.
The list stays as a checklist and shrinks as fields are fixed. Per-field
errors now appear once you leave a field rather than immediately, so a
freshly opened form is quiet.

Make the asterisk mean one thing: every required field carries it and
nothing else does. Count was missing one. A "* required" legend says what it
means, and the controls carry aria-required/aria-invalid so it is not
conveyed by a glyph alone.

The extra-fields editor in settings follows the same convention: asterisks
on key, name and choices, and a save error now outlines and focuses the
field it came from.
2026-08-12 19:53:11 +02:00
Donkie 740b1ad808 Merge pull request #1070 from Donkie/client-v2-vendor-extra-fields
client_v2: edit the manufacturer's extra fields when the add flow creates one
2026-08-12 19:51:38 +02:00
Donkie 379354159a Merge pull request #1068 from Donkie/client-v2-searchable-menus
client_v2: make the long toolbar menus searchable
2026-08-11 22:48:02 +02:00
Donkie e9b06d91c7 Merge pull request #1067 from Donkie/worktree-issue-1059-transparency
client_v2: bring back translucent filament colors
2026-08-11 22:43:53 +02:00
Donkie fb723f0af8 client_v2: stop the alpha slider tiling colour under its border
A `background` shorthand resets background-origin to the padding box, so
the slider's gradient was sized to the padding box but painted out under
its 1px border — and backgrounds repeat, so the strip under the border
showed the far edge of the next tile: a hairline of full-opacity colour
down the transparent end. It surfaced wherever the border doesn't cover
that strip exactly, i.e. Firefox at fractional display scaling.

Anchor every layer to the border box, and keep the colour gradient from
repeating at all.
2026-08-11 22:35:15 +02:00
Micha 7a31afaa39 Translated using Weblate (German)
Currently translated at 50.8% (273 of 537 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/de/
2026-08-11 20:28:29 +00:00
Donkie b2c46e22c7 Merge pull request #1065 from Donkie/client-v2-weight-presets
client_v2: quick-pick roll sizes when adding spools
2026-08-11 22:28:08 +02:00