Job board — editor self-claim ("swipe to accept")
Status: normative spec, operator-approved 2026-07-08. Pulls the dispatch half of Phase 10 forward: editors browse an open queue of ready jobs and claim them; the operator keeps manual assignment as an override. Decisions locked:
- Instant claim + admin override. Every footage-ready, unassigned
NEWjob is visible to eligible editors; Accept assigns it instantly (first taker wins, race-safe). Admin manual assignment in /ops still works (removes the job from the pool); admin can reassign after a claim. - Auto deadline. On claim,
internal_deadline = now() + packages.target_turnaround_hours(the[TARGET TURNAROUND]operator placeholder; jobs with no package/turnaround fall back to a tenant-config default — flag at build if absent). Admin may edit afterwards in /ops.
Hybrid dispatch (operator-approved 2026-07-08): both models coexist
Not every job goes to the pool — high-value work stays white-glove:
packages.dispatch_modeenum'pool' | 'managed', default'pool'. Premium packages are set tomanagedby the operator (tenant config, not code): their jobs NEVER appear on the board and are hand-assigned in /ops exactly as today. This is how "higher-value customers get assignment" is expressed — through the package they bought.- Per-job override:
jobs.dispatch_overridenullable enum'pool' | 'managed'beating the package default. /ops job detail gets a small toggle ("In pool / Withheld — assign manually") so the operator can pull any single job out of the pool or release a managed one into it. - Board visibility below therefore adds: effective dispatch mode
(
coalesce(jobs.dispatch_override, packages.dispatch_mode, 'pool')) must equal'pool'.
Who sees a job in the board
All of: job status = NEW · editor_id is null · footage-ready
(getFootageReadiness) · editor is vetting_status = approved + available
editor_tenantsrow for the job's tenant + under theirmax_concurrent_jobscap · editor has not dismissed it. RLS must enforce visibility server-side (new policy or an RPC — editors currently see only assigned jobs; this is a deliberate, narrow widening: pool-visible jobs expose ONLY board-card fields, never client identity).
The card (each available job)
Per the editor-flow UX research ("everything you need is here" completeness signal; pay before work starts):
- Pay, headline:
packages.editor_payout_gbpformatted in the tenant currency — never invented; renders the operator's placeholder until real values are set. If null → "Pay set at assignment — ask ops" (and flag). - Deadline preview: "Deliver within Nh of accepting" from
target_turnaround_hours; RUSH marker whenis_rush. - The brief: rendered via the existing
BriefRendererfrom the tenant template (requirements, style notes — whatever fields the tenant's template defines; this IS the "notes on styles": a template concern, not new schema). - Footage summary: file count, total size, container formats (from
files.r2_keyextensions +size_bytes). Duration/codec detail = Phase 9 (ffprobe worker) — show "N files · X GB" now, note the gap. - Tenant vertical label (slug/name) — pseudonymous both ways: NO client name/company/email anywhere on the board.
Interactions
- Accept (swipe right / button): POST → transition
NEW→ASSIGNEDwitheditorId = self. Server-side guards (transition machine, not UI): all visibility conditions re-checked + the existing optimisticstatus = NEWrace guard — the loser gets a clear "someone beat you to it" state, the card slides away. Auto-deadline set atomically in the same transition. Audit eventeditor_claimed(actor = editor). - Dismiss (swipe left / button): per-editor persisted hide — new table
editor_job_dismissals (editor_id, job_id, created_at)(migration 0006, RLS: editors write/read own rows). Reversible via a "Dismissed" filter. Dismissal is private — clients/ops unaffected (ops MAY see a dismissal count per job later; not in this slice). - Swipe mechanics: cards support drag-swipe on touch AND explicit Accept / Pass buttons (a11y + desktop parity; keyboard: ←/→). List-view toggle for editors who hate card stacks (research: professionals want queues, not games — the swipe is sugar, the list is the workhorse).
Where it lives
/workspace gains a second tab/section: "Available" alongside the
existing assigned-jobs list ("Yours"). Engine-dark surface, existing tokens
and components. Empty state: "Nothing available right now — new jobs appear
here the moment footage lands."
State-machine change (the one rules edit)
NEW→ASSIGNED permission widens from admin-only to admin OR
self-claiming eligible editor (editorId must equal the actor for the editor
path; all eligibility guards enforced in transitionJob). Unit gate
(tests/transitions.test.ts) extends accordingly — new arms for: editor
self-claim allowed, editor claiming FOR ANOTHER editor rejected, claim on
non-ready/assigned job rejected, over-cap claim rejected.
Ops console impact (minimal, this slice)
/ops unchanged except: jobs claimable by the pool show a small "in pool" indicator; manual assignment keeps working exactly as today and simply beats the pool. Reassignment flow unchanged.
Explicitly out of scope (this slice)
Auto-match/ranking (rest of Phase 10); push notifications for new jobs (Phase 7 email machinery); dismissal analytics; client-side of anything; editor payout changes (display only, placeholder values); duration/codec probing (Phase 9).