Skip to content

feat: annotation and pre-label are per job, and the gallery is an accordion of jobs - #783

Merged
JArmandoAnaya merged 6 commits into
mainfrom
feat/per-job-entry
Aug 26, 2026
Merged

feat: annotation and pre-label are per job, and the gallery is an accordion of jobs#783
JArmandoAnaya merged 6 commits into
mainfrom
feat/per-job-entry

Conversation

@JArmandoAnaya

Copy link
Copy Markdown
Contributor

What changed

The annotation job becomes the unit a person enters and the unit a model pre-labels, and the batch gallery shows a batch's frames one job at a time.

Pre-label is a job operation. POST /jobs/{job_id}/pre-label queues one annotation.pre_label row for one job (202 with a Location header, the contract the batch route had), keyed by annotation_job_id beside batch_id in the row's payload. POST /batches/{id}/pre-label and POST /projects/{id}/batches/pre-label stay, as fan-outs over each batch's open jobs, answering PreLabelFanOutOut — one row per job with annotation_job_id, the queued or joined background job, and joined. A finished job is passed over by a fan-out and refused by the job route (409 JOB_FINISHED). JobOut.pre_label_run remembers a job's newest run; BatchOut.pre_label_run is the newest across its jobs. The kernel declares JobAction.PRE_LABEL on an open job of an in_annotation batch (JOB_GATES, on BatchAction.PRE_LABEL's precedent); the CLI gains visionset job pre-label and the MCP server pre_label_job, and the batch and project verbs of both report one outcome per job. The plan stays batch-addressed — GET /batches/{id}/pre-label — because it is a property of the batch's pin and the model. A queue row enqueued before annotation_job_id existed is skipped by readers and fails its worker with a sentence saying why.

The gallery enters by job. The header carries Start annotating only for an approved batch — the approved → in_annotation transition, without an icon. Once a batch is open, the gallery is an accordion of its jobs (JobPanels.tsx): at most one panel is open, every panel may be closed, and the panel open on arrival is the first job with work left. A collapsed header states Job N · F frames · state · A of F annotated · assignee with a progress bar; the open panel carries the door (Annotate takes a pending job to in_progress and opens it; Continue and View only open), Pre-label gated on the job's allowed_actions, the assignee, segment chips counted from GET /jobs/{id}/progress, the order select, that job's timeline and only that job's frames, read through the new job filter on GET /batches/{id}/assets (BatchService.asset_page(job=), also on the MCP list_batch_assets). The thumbnail-size control is one shared setting on the header's caption line. A draft batch keeps the flat grid and its membership tools. The project's Annotate control opens a batch's one job directly and the gallery otherwise; the dropdown stays batch-level. The virtualised grid, selection and bulk bar moved unchanged into FrameGrid.tsx; JobsStrip/JobRow are gone.

Docs and rules. docs/content/ (batches, jobs, api, background-jobs, cli, mcp, ui, inference, architecture/backend/jobs) describe the job as the unit; mcp-tools.md, openapi.json, the generated client and the wire fixtures are regenerated. The batch-lifecycle skill's decision 10 now names a job-level pre-label action; the information-architecture skill records that Annotate enters by job and that a batch with jobs shows one job's frames at a time.

Found, not fixed

  • JobPanels.tsx and GalleryScreen.tsx import each other (Toolbar, Timeline, StartJobButton still live in the screen module). Inert today — only hoisted function declarations cross the edge — and the fix is to move those three into their own module.
  • A collapsed panel's header names an aria-controls id that is not mounted; the panel is unmounted rather than hidden so its filter and selection reset on switch.
  • BatchOut.of keeps a pre_label_run=None default; JobOut.of no longer has one.

Test plan

  • uv run pytest tests/kernel tests/inference tests/jobs tests/server tests/cli tests/mcp tests/architecture — green (the documented-refusals gate included)
  • uv run mypy src/visionset (175 files), uv run lint-imports (4/4), uv run ruff check . && uv run ruff format --check .
  • scripts/export_openapi.py, pnpm generate:client:check, scripts/export_wire_fixtures.py, scripts/export_mcp_tools.py --check — no drift
  • pnpm -r build && pnpm -r test && pnpm -r lint — annotator 1071, ui-core 1331, app 11
  • Playwright gallery, project-nav, annotate, navigation, viewport, visual; pnpm cycle (real server: approve → open the job from its panel → annotate → back reads in progress/Continue)
  • CI: the full matrix, including the browser cycle job

…be one job's

The annotation.pre_label queue row names its annotation job beside its batch;
PreLabelRun carries annotation_job_id and a row without it is skipped by readers
and refused by the handler. pre_label() and the handler take a job; JobAction
declares pre_label on an open job of an in_annotation batch, on
BatchAction.PRE_LABEL's precedent. JobService gains require_pre_labelable,
live_job, latest_pre_label_run and pre_label_runs; open_jobs_of is the fan-out
primitive. BatchService.asset_page keeps only one job's assets when asked.
…w per open job

POST /jobs/{job_id}/pre-label queues one run (202, Location) and refuses a
finished job. The batch and project launches answer PreLabelFanOutOut, one row
per open job with annotation_job_id, the queued or joined background job and
joined. JobOut carries pre_label_run; PreLabelRunOut names its annotation job.
GET /batches/{batch_id}/assets takes job= to keep one job's assets, 404
JOB_NOT_FOUND for a job the batch lacks and the empty page over a draft. The
launch logic the three routes share lives once in routes/_prelabel.py.
…n job

visionset job pre-label and the pre_label_job tool run one job; the batch and
project verbs loop each batch's open jobs and report one outcome per job, with
its job_id. Job outputs on both surfaces carry pre_label_run, and
list_batch_assets takes job_id.
Once a batch is open its frames are shown per job: at most one panel open,
every panel closable, the first job with work left open on arrival. A collapsed
header states the job's frames, state, annotated count, assignee and a bar; the
open panel carries the door (Annotate takes a pending job and opens it;
Continue and View only open), Pre-label gated on the job's allowed_actions, the
assignee, segment chips counted from the job's own progress, the order select,
the job's timeline and only its frames. The thumbnail-size control is one
shared setting on the header's caption line. The header keeps Start annotating
only as the approved to in_annotation transition. The project's Annotate
control opens a batch's one job directly and the gallery otherwise. The
virtualised grid, selection and bulk bar move unchanged into FrameGrid;
JobsStrip and JobRow are gone.
@JArmandoAnaya
JArmandoAnaya merged commit 9399430 into main Aug 26, 2026
15 checks passed
@JArmandoAnaya
JArmandoAnaya deleted the feat/per-job-entry branch August 26, 2026 08:09
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.

1 participant