Tick path
- Bump STALE_CLAIM_MS from 10 to 30 minutes. A 29-source OCR batch
hit ~29 min and was being reclaimed mid-run by the 10-minute cap,
leaving the batch in inbox with last_error='reclaimed after a
stalled run' even though nothing was stalled. MAX_ATTEMPTS still
bounds a truly stuck batch.
- After each dn_batch_pages create, the staged runner bumps
dn_batches.page_count in the same lease-fenced batch. Final
assembly write at run-staged.ts:223 still sets the authoritative
value, so any drift from a reclaim-then-restart cycle is corrected.
Inbox tiles now show live progress instead of 0 pages for the
entire reading window.
PocketBase batch transport
- sendRecordBatch chunks across pb.createBatch() calls at the
server 50-request cap. A 71-candidate assembly sent as one
request was returning Invalid batch request data. and 500ing —
the assembly route caught it and surfaced as the generic Grouping
could not be saved. 73 mutations now split into 50 + 23 and land.
Assembly save UX
- The dismiss dialog now renders directly under the open-pages alert
instead of at the bottom of the section (which was hidden behind 71
document tiles on big batches). Clicking Dismiss page opens the
reason input where the user is looking.
- saveGrouping surfaces the server-side error code
(stale_revision / posted_candidate / dismissed_candidate /
assembly_in_progress / invalid_page_coverage) instead of a generic
Try again. On success, the page now redirects to /inbox so the
documents land in their columns.
- The Technical details disclosure is gone from the grouping screen.
Open pages already surface their reason in the alert above; the
duplicate disclosure just competed with the decision UI.
Assembly route correctness
- After a successful manual save the route now writes
stage=complete, status=needs_attention, notes_detected,
processing_completed_at and clears claimed_at/last_error. The
runner advances stage on its own runs but a manual save was
never followed by a runner pass — without this the batch stayed
in Check page grouping forever even though assembly was done.
- For existing candidates the route only updates grouping metadata
(pages, page_from, page_to, assembly_revision, status, stage,
checks). OCR-derived fields (doc_number, supplier_name_raw,
items, raw_text) are preserved on the row — the runner only
re-fills those for batches in status=inbox, and post-save the
batch moves to needs_attention and never reaches the runner
again. New candidates still get a createdCandidateFields row
with the OCR fields blank, ready for the runner or operator
entry.
- A Re-run batch button was added to the document detail header
so the operator can refill the wiped OCR data (or re-resolve
checks after a manual override) without first navigating to the
batch detail page.
Customer identity check
- A Skip this check toggle on the document detail page records
manual_overrides.skip_customer_identity. run-staged reads the
override and turns the customer_identity check into passed=null
(skip) instead of passed=false (fail), so OCR false-positives
on the customer VAT no longer block posting while still
requiring an explicit operator decision.
- The PATCH route accepts skip_customer_identity as an editable
field so the toggle saves through the standard note route.
Tests
- 180 files / 1767 tests pass; typecheck clean.