feat: annotation and pre-label are per job, and the gallery is an accordion of jobs - #783
Merged
Conversation
…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.
…is an accordion of jobs
This was referenced Aug 26, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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-labelqueues oneannotation.pre_labelrow for one job (202 with aLocationheader, the contract the batch route had), keyed byannotation_job_idbesidebatch_idin the row's payload.POST /batches/{id}/pre-labelandPOST /projects/{id}/batches/pre-labelstay, as fan-outs over each batch's open jobs, answeringPreLabelFanOutOut— one row per job withannotation_job_id, the queued or joined background job, andjoined. A finished job is passed over by a fan-out and refused by the job route (409JOB_FINISHED).JobOut.pre_label_runremembers a job's newest run;BatchOut.pre_label_runis the newest across its jobs. The kernel declaresJobAction.PRE_LABELon an open job of anin_annotationbatch (JOB_GATES, onBatchAction.PRE_LABEL's precedent); the CLI gainsvisionset job pre-labeland the MCP serverpre_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 beforeannotation_job_idexisted is skipped by readers and fails its worker with a sentence saying why.The gallery enters by job. The header carries
Start annotatingonly for anapprovedbatch — theapproved → in_annotationtransition, 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 statesJob N · F frames · state · A of F annotated · assigneewith a progress bar; the open panel carries the door (Annotatetakes apendingjob toin_progressand opens it;ContinueandViewonly open),Pre-labelgated on the job'sallowed_actions, the assignee, segment chips counted fromGET /jobs/{id}/progress, the order select, that job's timeline and only that job's frames, read through the newjobfilter onGET /batches/{id}/assets(BatchService.asset_page(job=), also on the MCPlist_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'sAnnotatecontrol 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 intoFrameGrid.tsx;JobsStrip/JobRoware 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. Thebatch-lifecycleskill's decision 10 now names a job-level pre-label action; theinformation-architectureskill records that Annotate enters by job and that a batch with jobs shows one job's frames at a time.Found, not fixed
JobPanels.tsxandGalleryScreen.tsximport each other (Toolbar,Timeline,StartJobButtonstill 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.aria-controlsid that is not mounted; the panel is unmounted rather than hidden so its filter and selection reset on switch.BatchOut.ofkeeps apre_label_run=Nonedefault;JobOut.ofno 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 driftpnpm -r build && pnpm -r test && pnpm -r lint— annotator 1071, ui-core 1331, app 11gallery,project-nav,annotate,navigation,viewport,visual;pnpm cycle(real server: approve → open the job from its panel → annotate → back readsin progress/Continue)