Squash merging replaces Weblate's commits with a new one whose hash, and
for multi-commit PRs whose patch-id, matches nothing Weblate knows. Its
clone then rebases those commits onto a master that already contains the
same strings and hits a content conflict, after which Weblate stops
syncing until it is reset by hand. PR #1004 squashed two Russian commits
and broke ru/common.json exactly this way.
A merge commit keeps each Weblate commit reachable at its original hash,
so the rebase drops them as already-upstream. Nothing required squash
here: allow_merge_commit is on, the master ruleset has no linear-history
rule, and human PRs already land as merge commits.
The manufacturer was decided once, in the add-spool modal, and there was no
way back: a filament created under the wrong brand — or under none — stayed
there, and since the library groups by manufacturer that mis-files every
spool of it. The companion to the spool's change-filament (#1010), one level
up the chain.
Adds a "Change" action to the MANUFACTURER section of the filament
inspector, opening a dialog with the three destinations the API allows: one
of your manufacturers, a new one named on the spot (as the add-spool form
already allows), or none at all. There is no SpoolmanDB side to this one —
the external catalog carries filaments and materials, not manufacturers — so
the picker fetches the vendor list whole and filters it in the browser.
The dialog spells out the one consequence that isn't obvious. A filament
with no empty spool weight of its own takes its manufacturer's, and that is
the figure `PUT /spool/{id}/measure` subtracts from a reading off the scale,
so the note says what it becomes — or that it is about to have none. A
filament that has its own keeps it and gets no note: unlike the spool's full
weight, that field is editable right there in the panel.
Clearing the manufacturer travels as an explicit `vendor_id: null`, which is
what the API reads as "no manufacturer"; dropping the key instead would have
made that choice a silent no-op.
A collapsed "×N unused" row speaks for its whole pile with one filament's
name, swatch, weight and location, all read off the first spool. That only
holds within a filament group. Under Manufacturer (or Material/Location) a
group spans many filaments, so the whole unused pile was filed under
whichever filament happened to sort first: five different SUNLU woods
listed as "Wood Cherry ×5 unused" (#1012).
Bucket the pile by filament before collapsing it, keeping the backend's
order via first appearance. Buckets of one render inline as before.
Fixes#1012
The v1 client offered the spool's empty-spool weight and its filament's as two
separate label tokens. v2 kept only the filament's, so `{spool_weight}` in a
migrated v1 design had no equivalent to map to and resolved to "?" — and a
design rebuilt in v2 could only print the filament's figure, which is not
necessarily the one that spool is measured against.
`spool.spoolWeight` falls back to the filament the way `spool.price` already
does, so a `spool.*` token prints what actually applies to that spool.
Accent-coloured pills next to four fields read as warnings about something
being wrong, when all they report is that a spool holds a value of its own.
Keep the accent colour, which is what makes them findable, and drop the border,
weight and uppercasing that gave them the urgency.
Both ends now render identically. The winning and shadowed levels were
distinguished by colour, but the wording already says which is which
("Overrides filament" vs "Overridden by this spool"), so the variant carried
nothing the label didn't.
A spool can hold its own full weight, empty-spool (tare) weight and price,
falling back to its filament's where it has none. The API has had all three
since #339, and the v1 client exposed them, but client_v2 never mapped
spool_weight at all and had no field for it or for the full weight.
The result was silent wrong arithmetic (#1013): the API gives every spool it
creates a copy of the filament's tare weight, so correcting the *filament*
later looked like it had worked — the spool panel showed the new figure — while
"adjust by measured weight" went on subtracting the old one. A reporter's empty
261 g spool came back with 129 g still on it, exactly the difference.
Both ends of a disagreement are now marked. The spool's field says what it
shadows, the filament's row says what applies instead and is struck through,
and the same treatment covers the manufacturer's empty-spool weight, which
seeds a filament and is then left behind. Only where the values actually
differ: merely having one of its own is already visible in the field, and
marking every spool would teach everyone to stop reading the marks.
Also fixes a 500 from PUT /spool/{id}/measure when nothing in the chain has a
tare weight — reachable in one click now that the field can be cleared.
The v1 client had a filament dropdown on the spool edit form; client_v2
dropped it, leaving no way to re-point a spool at a different filament
(#1010). People who organise their shelves by spool number need slot #37
to stay slot #37 when the roll in it is replaced, and deleting and
re-adding the spool hands them a new id.
Adds a "Change" action to the FILAMENT section of the spool inspector,
opening a dialog that searches the local catalog and SpoolmanDB (an
external pick is imported on apply, as in the add-spools flow). The spool
keeps its id, location, dates, lot, comment and used weight.
The full weight needs a decision the dialog makes explicit: a spool
created from a filament records that filament's weight as its own, so it
would otherwise keep the old figure forever and this client has no other
field for it. When the new filament's weight differs, the dialog offers
to take it (checked by default) and spells out the resulting remaining
weight. A spool with no weight of its own follows its filament anyway —
that case just says so.
The ci hook lints, formats and type checks client_v2 but never ran its unit
tests, so everything under src/**/*.test.ts was local only and nothing would
have caught a broken one. The backend group already runs pytest here, so this
is just the missing counterpart. The browser suite in tests_frontend_v2/ is
unaffected, it covers different ground and runs in its own job.
Adds about 4s to the style job, which already installs client_v2 deps. Left
out of pre-commit so committing stays fast.
Also points the type check comment at the pre-commit copy it meant, rather
than at itself.
Behind a forward-auth proxy (Authelia, Authentik, oauth2-proxy) an idle
tab starts getting 401s once the session expires. The proxy can only
redirect a top-level navigation, so a page that is already loaded never
gets offered the login portal and simply fails every request until the
user works out that a manual reload fixes it.
Do that reload for them. It counts as a navigation, so it earns the
redirect and brings the user back to the page they were on. Rate limited
to once per 30s so a 401 that a reload cannot fix does not put the tab in
a loop, and skipped for writes so a half filled form is not lost to a
login redirect.
Both transports are covered. REST goes through http.ts. Websockets get an
HTTP probe instead, since a handshake refused by a proxy exposes no status
to the browser and is otherwise indistinguishable from a backend that is
down. search.ts moves onto the shared fetch helper so it is covered too.
Dormant without a proxy in front, since Spoolman never answers 401 itself.
Refs #923
The validator fix only guards the write path. Instances that already hit #780
keep 500ing on GET /filament because the stored value exceeds the response
model's max_length, and the UI can't repair what it can't list.
Filament.from_db now strips the # and returns None when the stored value still
isn't a valid 6/8 character code, so a single bad row no longer takes down the
whole list. A migration chained from 415a8f855e14 rewrites existing rows one by
one through op.get_bind() rather than with SQL string functions, so it behaves
the same on sqlite, postgres, mysql and cockroachdb. downgrade() is a no-op.
Searching a filament name only ever returned the filament: a spool matches
on its own text (comment, location, lot number, extra fields) or its exact
id, so a name that lives on the filament could not reach any of its spools.
The hit named the filament you own and then dead-ended.
Each filament result now carries its first few spools as pills, with an
overflow link to the filament for the rest. GET /search gains an opt-in
spools_per_filament parameter; at 0, the default, the response is byte for
byte what it was, so nothing else that reads the endpoint changes.
The spools are fetched in one query over the matched filaments and grouped
in Python rather than with a per-group window function, which keeps the
behaviour identical on all four supported databases, and yields the exact
total count for free. They obey allow_archived like the rest of the
response, and are ordered by id so "first" means the same thing everywhere.
Fixes#993
Choice values could only be appended, so the only way to get a value into
the right place was to remove everything after it and type it all back in.
Both clients now render each choice with a grip: drag it, or focus it and
press the left/right arrow keys, to move it. The backend already accepts a
reordered list — it only refuses removals.
Fixes#994
The material chip and the "matched: <field>" chip sat side by side at the
trailing edge with identical styling, so two unrelated facts read as one kind
of thing. Move material next to the filament name (mirroring the group header)
and give it an outline variant, leaving the filled chip to mean "why this
result matched".
The `useCurrency` and `useCurrencyFormatter` functions parse currency settings using `JSON.parse` directly on values fetched from the backend. If the backend returns malformed JSON or unexpected data types, this will throw an unhandled exception and crash the UI component relying on these hooks.
Signed-off-by: tomaioo <203048277+tomaioo@users.noreply.github.com>
The guard compared the base branch tip against the PR head directly, so
every commit that landed on master after a PR forked counted as one of
that PR every time it re-ran. Merging a Weblate sync therefore failed
unrelated PRs with client_v2/locales/sv/common.json in the offending
list, and the only way out was rebasing a PR that had never touched a
translation file.
Diffing BASE...HEAD resolves the merge base first, which is what "the
changes this PR makes" means, and it stays right however far master
moves on. Verified against PR 1001: the old form flags sv, the new form
passes, and a real hand-edit of sv is still caught.
The hover underline and colour already say the title is a link, and the
arrow was one more glyph on a row that has a chevron doing something
else. Touch users lose the at-rest cue, but the manufacturer link below
is visible without hover either way.
The old client could delete spools (canDelete on the resource, rendered
by Refine's Show header buttons); client_v2 shipped with archive only.
That left the filament delete a dead end: archiving does not release the
foreign key, and there is no way to reassign a spool either, so a
filament that had ever held a spool could never be removed.
The confirm warns when filament is left on the spool, since archiving is
probably what was meant in that case.
Also corrects the blocked-filament hint, which suggested moving spools
to another filament - something this UI cannot do.