Agents plan slip lines (not shifting indexes), may group lines into one
kit allocation, and NIL lines close automatically. Commits line by line
under the inbox's PO and batch leases; journaled and resumable.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Study of 18 Avidan delivery documents matched by hand against their POs,
with root causes for why nearly every slip is flagged. Design for giving
agents full delivery-note tooling (everything except delete), staged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add per-site CSV and PDF matrix reports, editable sick-note date ranges, and weekend calendar styling. Preserve verified duplicate uploads for audit while excluding their entries from hours and costs, and prevent repeat files and overlapping daily imports.
Remove repeated Plant, Financial and Workforce shortcut cards. Add site-scoped Plant and Financial sidebar attention status with automatic refresh.
Include accumulated delivery processing, purchase order recovery, supplier reconciliation, plant data and user management changes and audit records.
Fresh duplicate check before posting, dismiss route sets stage, free accessory
lines no longer crash promotion, readiness treats only posted notes as terminal,
document-reference/usable-PO helpers, PO delivery reconciliation API + docs and
audit register.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Generalizes the Plant page's add-dialog into a shared AddVehicleButton
component parameterized by unit_type, and wires it into the vehicles
page against the existing POST /api/vehicles endpoint.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.
- Ask's supplier loader didn't pass date_received, so supplier_balances
and the supplier_balance dataset crashed
- supplier_balances takes a period (thisMonth, thisWeek, month1-3) and
returns what was received from each supplier in it (payable)
- PocketBase's space-separated dates dropped deliveries on the 1st from
period filters, including the Suppliers page's Payable KPI
- The PDF route falls back to the answer text when a saved report has
no sections (older format), instead of a 500
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Ask page sized itself as 100svh-4rem, but the Topbar is 89px on
desktop and 167px on phones, so the page overflowed and the bar scrolled
under the header. The Topbar now publishes its live height as
--topbar-h and the Ask page fills exactly the space below it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A turn can call the same tool more than once (e.g. two query_records),
which gave duplicate React keys and a console warning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Audited /dashboard, /dashboard/sites (+ detail), /dashboard/workforce/workers,
/dashboard/workforce/on-site, /dashboard/plant/vehicles (+ detail),
/dashboard/plant/equipment (+ detail), /dashboard/plant/fuel-logs,
/dashboard/financial/purchase-orders (+ detail),
/dashboard/financial/delivery-notes/inbox, /dashboard/financial/suppliers,
and /dashboard/chat at 375x812 and 768x1024 using a scroll-right test to
catch real horizontal page overflow (as opposed to a table's own,
intentional internal scroll).
Two repeating bug patterns accounted for every overflow found:
1. A page-specific `right={<div className="flex items-center gap-2">...}`
passed to Topbar can carry two or three labelled buttons (edit/import/
merge/back etc.). Without `flex-wrap` they don't fit a 375px screen in
one row and push the whole page sideways. Fixed on the vehicles,
equipment, and site detail pages, plus their list-page counterparts
(vehicles fuel-view, equipment plant-view) — all five instances of
this exact pattern. Same fix applied to a delivery-note card's button
cluster on the PO detail page, and to Topbar's own right-side
container as a general backstop.
2. `Intl.NumberFormat("en-ZA", ...)` joins currency/number groups with
non-breaking spaces, so a wide value like "R 24 988 663" can't wrap.
In a 3-up grid (site cards' Contract/Spent/Remaining) that overflowed
the column; fixed by stacking those to 1-up below `sm`. In a 2-up grid
(PO detail KPI tiles) added `min-w-0` so the tile clips instead of
growing the grid track past the viewport.
Also: the shared Dialog now caps at `max-h-[90dvh]` with internal
scroll (a dialog taller than the phone viewport had no way to reach
its close button or footer before), and the sidebar hamburger button
was overlapping the Topbar's title on every page below `lg` — reserved
space for it instead.
Tables already scroll horizontally inside their own card via the
shared `<Table>` component or an `overflow-auto` wrapper, and KPI
grids already collapse responsively — both were fine everywhere checked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found live: rendering a report PDF 500'd with "ENOENT ... data.trie".
pdfmake's server PdfPrinter pulls in pdfkit -> @foliojs-fork/fontkit,
which loads font-shaping tables from a path relative to its own
installed location at runtime; webpack-bundled into the route's chunk,
that lookup can't find them. serverExternalPackages leaves the whole
chain on native Node `require`, where the real node_modules layout
resolves them — the same reason Next already ships `sharp` and
`@react-pdf/renderer` externalized by default.
Also found live: a bar chart with ~30 categories (fuel by vehicle)
printed completely overlapping x-axis labels. categoryLabelStride
thins them the way recharts' minTickGap does, keeping only as many
labels as the plot width can fit without collision.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>