Skip to content

feat(app): a new label can be created from the annotation page (#233) - #242

Merged
JArmandoAnaya merged 2 commits into
mainfrom
feat/issue-233
Aug 3, 2026
Merged

feat(app): a new label can be created from the annotation page (#233)#242
JArmandoAnaya merged 2 commits into
mainfrom
feat/issue-233

Conversation

@JArmandoAnaya

Copy link
Copy Markdown
Contributor

A user who needed a class that did not exist had this path: leave the job, open
the Schema tab, publish a version, make a new batch — because the old one
pins the old version — and re-partition. The + in the tool palette now does it
in place, and the class is usable in the batch they are already working in.

Three calls, and the order is the design. Save the pending annotations, then
publish the next version, then re-pin the batch (#229). Workspace builds the
annotator store in a useMemo keyed on the schema, so the refetch after the
re-pin rebuilds it — publish before saving and the user's last few boxes are
gone with a success toast on screen, no error and nothing to see. Losing undo
history at a save boundary is the page's existing documented behaviour; losing
work is not, and the ordering is all that separates them.

So the chain is runAddClass, a function taking three thunks rather than a body
inside a useCallback — because behaviour that matters is worth testing without
a canvas. addClass.test.ts drives it with recorders and asserts the sequence;
flipping save and publish turns four named tests red, which is acceptance
criterion 4 verified by mutation rather than asserted.

The version is composed on the active version's classes plus the new one,
never on the batch's pin. Versions are linear and create_version takes the
whole contract, so composing on a stale pin would silently delete every class
published since — a destructive change nobody asked for. The dialog will not
submit before the active version has loaded, for the same reason.

patterns/ClassFields.tsx is the class form extracted from SchemaEditor.tsx,
which is what the issue asked for and what stops two forms drifting on which
geometries are offered, how a derived colour is shown, and how an attribute's
options are typed. It is fields, not a form — no submit, no card — because the
editor puts them in a draft array and the dialog in a single new class, and only
the part that has to agree is shared. slot carries the testid identifier, so
the editor keeps its class-name-2 ids and its 301 tests passed unchanged.

Three requests are not a transaction. What each failure leaves behind is stated
rather than hidden, and the last row is the one worth naming: if the re-pin
refuses, the version exists and the pin has not moved.
That refusal has no flag
by design — it means somebody else narrowed the schema past this batch's pin — so
the dialog names the Schema tab instead of offering a retry that cannot work.

The palette button is absent where a host cannot honour it (onOpenGallery's
rule), which is why there is no e2e scenario: the annotator demo has no project
behind it, so it renders no button — the criterion's own "else the ui-core
screen-test convention". It keeps the canvas's focus like every other palette
button, or every chord would be dead until the user clicked the picture again.

On success the new class becomes activeClass, which lives on the page rather
than in the store and so survives the rebuild; its digit hotkey arrives free
because the palette order is the hotkey order (#46).

322 ui-core vitest, up from 301.

Closes #233

A user who needed a class that did not exist had this path: leave the job, open
the Schema tab, publish a version, make a **new batch** — because the old one
pins the old version — and re-partition. The `+` in the tool palette now does it
in place, and the class is usable in the batch they are already working in.

**Three calls, and the order is the design.** Save the pending annotations, then
publish the next version, then re-pin the batch (#229). `Workspace` builds the
annotator store in a `useMemo` keyed on the schema, so the refetch after the
re-pin *rebuilds it* — publish before saving and the user's last few boxes are
gone with a success toast on screen, no error and nothing to see. Losing undo
history at a save boundary is the page's existing documented behaviour; losing
work is not, and the ordering is all that separates them.

So the chain is `runAddClass`, a function taking three thunks rather than a body
inside a `useCallback` — because behaviour that matters is worth testing without
a canvas. `addClass.test.ts` drives it with recorders and asserts the sequence;
**flipping save and publish turns four named tests red**, which is acceptance
criterion 4 verified by mutation rather than asserted.

The version is composed on the **active** version's classes plus the new one,
never on the batch's pin. Versions are linear and `create_version` takes the
whole contract, so composing on a stale pin would silently delete every class
published since — a destructive change nobody asked for. The dialog will not
submit before the active version has loaded, for the same reason.

`patterns/ClassFields.tsx` is the class form extracted from `SchemaEditor.tsx`,
which is what the issue asked for and what stops two forms drifting on which
geometries are offered, how a derived colour is shown, and how an attribute's
options are typed. It is *fields, not a form* — no submit, no card — because the
editor puts them in a draft array and the dialog in a single new class, and only
the part that has to agree is shared. `slot` carries the testid identifier, so
the editor keeps its `class-name-2` ids and its 301 tests passed unchanged.

Three requests are not a transaction. What each failure leaves behind is stated
rather than hidden, and the last row is the one worth naming: **if the re-pin
refuses, the version exists and the pin has not moved.** That refusal has no flag
by design — it means somebody else narrowed the schema past this batch's pin — so
the dialog names the Schema tab instead of offering a retry that cannot work.

The palette button is absent where a host cannot honour it (`onOpenGallery`'s
rule), which is why there is no e2e scenario: the annotator demo has no project
behind it, so it renders no button — the criterion's own "else the ui-core
screen-test convention". It keeps the canvas's focus like every other palette
button, or every chord would be dead until the user clicked the picture again.

On success the new class becomes `activeClass`, which lives on the page rather
than in the store and so survives the rebuild; its digit hotkey arrives free
because the palette order *is* the hotkey order (#46).

322 ui-core vitest, up from 301.

Closes #233
…he dialog is open

`e2e/annotate.spec.ts` asserts that opening a job makes **no** request to
`/schema` — the page is judged against the batch's pinned version, and one that
read the active version would offer classes the API then refuses. #233's
unconditional `useActiveSchema` broke that, and the test was right to fire.

`useActiveSchema` gains an `enabled` parameter, defaulting to on so the rule
holds unless a caller says otherwise. The dialog is the one place the active
version is the right question, so it is the one place that asks — and only while
it is open.

Refs #233.
@JArmandoAnaya
JArmandoAnaya merged commit fc1f8ad into main Aug 3, 2026
13 checks passed
@JArmandoAnaya
JArmandoAnaya deleted the feat/issue-233 branch August 3, 2026 01:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(app): a new label can be created from the annotation page

1 participant