Skip to content

UI: schema class-removal flow — three dialogs, contradictory promises, raw API text #588

Description

@JArmandoAnaya

Removing a label class that has annotations walks the user through three dialogs that contradict
each other
, and two of them render raw API text — a machine code, a project UUID, and SDK keyword
spelling.

All citations are at e61d3ed, in frontend/ui-core/src/screens/SchemaEditor.tsx.

The observed flow

Dialog 1 — RemoveClassDialog (:892). Correctly predicts the refusal, in the user's face, and
then offers to proceed anyway. Its own docstring at :880-890 says it "is not asking permission for
something that will then work: it is saying, before a version is composed that cannot be published,
that this is where it will fail" — and the primary button is nonetheless "Remove from draft"
(:920). The copy at :913 reads:

N annotations already use "lane". Saving a version without it is refused outright —
SCHEMA_CHANGE_WOULD_ORPHAN has no override — so this removal cannot be published until those
annotations are gone.

A machine code in a sentence a person reads, and a confirm button that starts a path the same
sentence says is doomed.

Dialog 2 — DestructiveDialog (:927). Reached on save. Title "This narrows the schema", body
message={failure?.message ?? ""} (:588) — the raw kernel sentence, which carries the project UUID
and the phrase pass allow_destructive=True to proceed. Its second paragraph promises: "Saving
anyway publishes the new version and leaves earlier ones exactly as they are."
Primary button
"Save anyway" (:961).

Dialog 3 — OrphanDialog (:977). Reached after pressing "Save anyway". Title "Annotations
already use these classes", body message={failure?.message ?? ""} (:601) — again the raw kernel
sentence with the project UUID. There is no confirm button, correctly, and the copy says "There is no
override for this one."

So dialog 2 promises publication and dialog 3 refutes it, one click apart. The user was told the
outcome accurately in dialog 1, then walked through two more to arrive back at it.

What this violates

  • DESIGN.md copy rules (:759-766). "An error is what happened plus what to do, one sentence
    each. The API's error contract already separates the two (docs/api.md); the UI does not merge them
    back together." Here the UI does not separate them at all — it renders error.message verbatim,
    including a UUID and an SDK keyword argument.
  • DESIGN.md principle 9 (:148) and ui-capabilities' fourth banned pattern: "Refusals
    render through the shared code→prose vocabulary (one map, product-wide). A bare
    BATCH_NOT_IN_ANNOTATION badge is not a message." SCHEMA_CHANGE_WOULD_ORPHAN appears as literal
    user-facing copy at :913.
  • DESIGN.md "Destructive actions state their blast radius" (:642-647): a confirmation names
    what will be destroyed, counted. Dialog 2's blast radius is whatever prose the kernel happened to
    interpolate.
  • ui-capabilities' one rule — the frontend renders what the wire declares. Dialog 1's count comes
    from ProjectService.stats via a separate query (SchemaEditor.tsx:315-317), not from anything that
    declares removability. cf. server/wire: schema orphan and narrowing refusals are prose-only, and the wire declares nothing ahead of time #587.

Target

One terminal dialog at the moment of intent for a class that has annotations. It states the fact
(N annotations use "lane"), the way out (delete or relabel them first), and links to the
annotations filtered to that class. No doomed path — no button that begins a save the dialog
already knows will be refused.

A real narrowing confirmation for a class with no annotations: blast radius counted per
DESIGN.md:642, one confirm, and the save succeeds.

Neither dialog renders a machine code, a UUID, or SDK keyword spelling. Refusals that still reach the
UI render through the shared code→prose vocabulary.

Blocked on

Do not start this before both are settled.

Provenance

Found by a read-only audit of the schema-narrowing / orphan gate at e61d3ed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingfrontendannotator / ui-core / app packagesuxUser experience / interaction design

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions