Skip to content

feat(annotator): the read-only mode stops advertising edits — no classes region, no handles, no move cursor, and selection reflected in the panel - #431

Merged
JArmandoAnaya merged 1 commit into
mainfrom
feat/readonly-presentation
Aug 8, 2026
Merged

feat(annotator): the read-only mode stops advertising edits — no classes region, no handles, no move cursor, and selection reflected in the panel#431
JArmandoAnaya merged 1 commit into
mainfrom
feat/readonly-presentation

Conversation

@JArmandoAnaya

Copy link
Copy Markdown
Contributor

Closes #426

What changed

The four presentation rules of the annotation workspace's read-only mode (decision of 2026-08-07, quoted in the issue). feat, not fix: the mode never had these rules — the classes region rendered there by #420's explicit render-as-information direction, which the decision supersedes, and the cursor/handle behaviour was never specified for the mode at all.

  • (a) No classes region. AnnotatorPanel renders the region and its split rule only outside the read-only mode; the objects region takes the whole panel by the flex rule that always sized it. The region's refusal machinery — including the disabled-with-reason rows and the door-gating Completed batch opens editable: edit functions remain active in the read-only workspace #423 added — leaves with it: a region that only exists where arming is legal needs no refusal path, so ClassRegion, ClassRow and the page's classRefusal plumbing all lose the prop. C stays claimed and does nothing (the filter it focuses does not exist); the digits were already swallowed by READ_ONLY_KINDS.
  • (b) Default cursor everywhere. New core function viewerAffordanceAt (core/interaction/affordance.ts): cursor default always, hot body kept for the hover highlight — a highlight aids selection, a cursor change advertises an edit. The adapter branches to it on readOnly.
  • (c) No handles on selection. AnnotationLayer and AnnotationShape take a handles flag; the canvas passes !readOnly. Selection keeps the selected treatment — stroke 3 and the label. TransientLayer passes true unconditionally: it only ever draws a shape a gesture holds, and no gesture exists in the mode.
  • (d) Selection is one state, reflected everywhere. At HEAD neither mode synced: edit mode highlighted the row but never scrolled it into view, and the read-only mode swallowed every primary press, so canvas-side selection did not exist there at all. Both are fixed: the viewer's primary press now selects (resolved by topmostAnnotationAt — the same rule the hover highlight and the right-click menu use, so the three cannot disagree) and every ObjectRow scrolls itself into view when its selected arrives, in both modes. DOM focus deliberately stays on the canvas root, which reads the chords — "moves focus coherently" is read as the row highlighting and scrolling into view, not as a focus steal that would kill the keyboard.

DESIGN.md §"The annotation workspace" gains a "The read-only mode" subsection recording all of it, including #423's banner rule — the section previously did not describe the mode.

One explanation surface is preserved: the read-only banner (with its correction-batch link, cf. #423) stays the only place the mode explains itself; nothing here adds a second.

Found, not fixed

Test plan

All proven in chromium except the pure functions:

  • affordance.test.ts — five new tests for viewerAffordanceAt / viewerPressTarget (default over a grip, default-vs-move against the editor, empty canvas, press-equals-highlight, topmost rule).
  • panel.test.tsx — "renders no classes region at all — absent, not disabled"; "scrolls the selected row into view when the selection arrives from outside".
  • topBar.test.tsx — the settled-frame test now asserts absence (it pinned the superseded Class selection moves from the top bar into a split right panel #420 direction).
  • e2e/annotate.spec.ts — four new: region absent + objects region measured at full panel height + c reaches nothing; read-only selection shows no move cursor and no handles; the editor control (move cursor + grips still offered); canvas selection scrolls the row into view under a 14-object overflow.
  • cycle/cycle.spec.ts — the real-kernel read-only step now asserts the region's absence.

Mutation verification, each by its exact diff, reverted after, tree byte-identical to the commit at the end:

Mutation Named test that went red
handles={!readOnly}{true} e2e "read-only selection shows no move cursor and no handles"
viewer cursor defaultmove (core) 2 × affordance.test.ts + the same e2e
classes region unconditional panel.test.tsx absence + topBar.test.tsx settled-frame
scroll-into-view removed panel.test.tsx "scrolls the selected row into view"
viewer press selects nothing the same e2e, on its row-selected assertion

Gate stages (staged; exit codes verbatim):

Stage Exit
uv run pytest -q (green except the ten ffmpeg-environment tests recorded on #429) 0
uv run ruff check . / uv run ruff format --check . 0 / 0
uv run mypy src/visionset / uv run lint-imports 0 / 0
pnpm -r build / pnpm test / pnpm -r lint (all three annotator boundary gates) 0 / 0 / 0
openapi drift / generated client drift / mcp reference drift / version sync 0 / 0 / 0 / 0
browser e2e (CI=1 playwright test, 226 tests) 0
browser cycle (CI=1 playwright test -c playwright.cycle.config.ts) 0

…ses region, no handles, no move cursor, and selection reflected in the panel (#426)
@JArmandoAnaya
JArmandoAnaya merged commit c2f8ec0 into main Aug 8, 2026
13 checks passed
@JArmandoAnaya
JArmandoAnaya deleted the feat/readonly-presentation branch August 8, 2026 07:51
JArmandoAnaya added a commit that referenced this pull request Aug 21, 2026
…ses region, no handles, no move cursor, and selection reflected in the panel (#426) (#431)
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.

Read-only workspace: no class panel, no edit affordances, selection syncs to the panel

1 participant