Skip to content

fix(annotator): the withheld Finish job explains itself — a focusable tooltip carrying the unresolved count - #432

Merged
JArmandoAnaya merged 1 commit into
mainfrom
fix/finish-job-tooltip
Aug 8, 2026
Merged

fix(annotator): the withheld Finish job explains itself — a focusable tooltip carrying the unresolved count#432
JArmandoAnaya merged 1 commit into
mainfrom
fix/finish-job-tooltip

Conversation

@JArmandoAnaya

Copy link
Copy Markdown
Contributor

Closes #427

What changed

The withheld Finish job on the last frame now explains itself properly (DESIGN.md principle 9, with principle 4 riding on it):

  • A real tooltip, not a title spread. The reason renders through the product's tooltip primitive; the title attribute is gone from the control.
  • aria-disabled, never the native attribute, for the withheld state. ZoomWidget's finding, applied here: a natively disabled <button> receives no pointer events and cannot take focus, so its reason could never open. The press is refused in the handler instead. Native disabled survives only where there is nothing to explain — a Finished job (the label is the explanation) and the in-flight press.
  • The copy names the blocker with its count: 3 frames unresolved — annotate or skip them to finish the job. (singular handled). The number is outstandingWork(counts) from batchState.tsunannotated + review_pending, the two states whose settling is what makes the kernel declare complete — so the count and the readout share one progress source and no second derivation exists. The count-less sentence survives only while the counts query has not answered.
  • Enabled Finish job gets no tooltip; the disable itself stays the wire's (complete absent from the job's allowed_actions) — nothing here computes legality.

The e2e stub gains a jobSettled lifecycle knob so a job can honestly decline to declare complete, and the jsdom harness now answers /jobs/{id}/progress.

Found, not fixed

  • The Skip/Un-skip pair still carries its (rarely reachable) withheld reason as a native title; same pattern, separate control, not expanded here beyond the issue's scope.

Test plan

  • topBar.test.tsx — the principle-9 test now asserts aria-disabled + no title + tooltip prose on hover + a press that sends nothing; plus three new: the count copy (2 unannotated + 1 in review → "3 frames unresolved"), the singular copy, and no-tooltip-once-live.
  • e2e/annotate.spec.ts (chromium) — "a withheld Finish job explains itself on focus, with the count": tooltip opens on focus (principle 4), carries the correct count, and a forced click sends nothing to the wire. Playwright itself refuses to click an aria-disabled control, which is the assistive contract observed from outside; force is used to prove the handler behind it.

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

Mutation Named test that went red
count off by one (outstandingWork(counts) + 1) "names the blocker with its count…" and "speaks singular…"
withheld state back to native disabled "says why Finish job cannot be pressed where it does render"

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 0 / 0 / 0
openapi drift / generated client drift / mcp reference drift / version sync 0 / 0 / 0 / 0
browser e2e (CI=1 playwright test, 228 tests) 0
browser cycle (CI=1 playwright test -c playwright.cycle.config.ts) 0

@JArmandoAnaya
JArmandoAnaya merged commit e6a0a6e into main Aug 8, 2026
13 checks passed
@JArmandoAnaya
JArmandoAnaya deleted the fix/finish-job-tooltip branch August 8, 2026 08:29
JArmandoAnaya added a commit that referenced this pull request Aug 21, 2026
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.

Finish job on the last frame: disabled state must explain itself

1 participant