The migration's docstring pointed at a spec file that was never
written for this feature (the design was worked out via conversation,
not a separate spec doc) — points at the plan instead.
A concurrent project Month/Week feature is landing in the same view
files and has already shifted purchasing-view.tsx by ~10 lines, adding
a periodFilter beside projectFilter. The removal steps now match on
identifiers and snippets, and the plan header says to treat every
file:line as a hint.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
16 TDD tasks: pure scope helpers, the getSiteScope seam, the topbar
switcher, then per-module server-side filtering. Task 15 records why
the chat agent's default-site change is deferred out of this plan.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replaces the four independent per-table project-code dropdowns with a
single global scope carried in the URL as ?site=, resolved server-side,
and applied as a PocketBase site= filter. Defines the getSiteScope()
seam so the separate PocketBase users work plugs in without touching
the 23 Topbar call sites.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds the "Upload status" buttons on Purchase Orders and Delivery Notes,
the shared badge/KPI/filter UI they use, and removes the now-dead
Uploads sidebar entry and Financial landing card.
Purchase Orders and Delivery Notes each get their own unlinked
"upload status" page reached via a button next to the upload action,
mirroring the Timesheets pattern. Removes the combined
/dashboard/financial/uploads page and its sidebar entry, since its two
halves (PO status, DN status) are now scoped to the page they belong to.
Timesheets now shows only entries + filters; an "Upload status" button
next to Import Timesheets opens the sheet-by-sheet processing view on
its own route, not listed in the sidebar.
Workers becomes a lean roster (name, employee number, position, type,
site, rate) with filters unchanged. Everything currently cluttering the
list row -- total hours, cost, the 8-week sparkline, per-employee entry
history -- moves to a new worker detail page that doesn't exist yet
(today's "detail" is a dialog). Same pattern already used for Suppliers
and Vehicles/Equipment this session: lean list, detail page carries
depth.
Timesheets keeps its flat entries table but gains a genuinely separate
upload-status section -- the timesheets document collection already has
this data, it's just currently squeezed into a per-row badge instead of
being its own thing. The duplicated Import-timesheets button (currently
on both pages) moves here only.
QR badges: encodes employee_no directly, no new PocketBase field --
every worker already has a valid code the moment they exist, with no
generation step. Explicitly a deliberate tradeoff (guessable numbers,
no revocation) the user chose for now, pending seeing how the separate
timekeeping PWA wants identity handled -- moving to a stored opaque
token later is additive, not a rework. Card layout confirmed via the
visual companion (centered, QR-forward). Both single-worker and
batch-sheet print confirmed in scope. Rendering via the qrcode npm
package + the browser's own print dialog (@media print), not a PDF
library -- CSS handles real-world card dimensions more reliably than
fighting a PDF engine's coordinate system.
The timekeeping PWA itself is explicitly out of scope -- a separate
project this spec only needs to stay compatible with.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Card is rounded-xl with no overflow-hidden. TableHeader is sticky with
its own opaque background (bg-card/95) that sits flush against the
card's edge -- with nothing clipping it, the header's square background
corners poke past the card's rounded ones. Visible on every page built
with the shared <Card><CardContent className="p-0"><Table> pattern:
Suppliers, Supplier Detail, Uploads, Delivery Notes, Fuel Logs,
Timesheets. Purchase Orders is unaffected -- it uses a card-per-row
layout, not an HTML table.
Added overflow-hidden to each affected Card. Standard, deterministic
Tailwind behavior -- reliably clips child content to the parent's
border-radius, no visual regression risk.
Also added click-to-sort on the Suppliers list (Supplier/POs/Ordered/
Delivered/Outstanding, toggling asc/desc) -- the page previously only
supported a fixed server-side default (outstanding descending) with no
way to change it. No existing sort-header convention exists elsewhere
in the app, so this is self-contained rather than a premature shared
abstraction for what is still a single use site.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
13 tasks across 6 phases: additive schema, pure matching/ledger
functions (TDD, tested in isolation), the one irreversible migration
(guarded, dry-run default), OCR wiring, API routes, and UI.
Grounded in the actual codebase rather than the spec's prose: the
existing "unknown supplier" inline-editor on the PO detail page
(SupplierEditor + POST /api/purchase-orders/[id]/supplier) is upgraded
in place rather than duplicated, following the same principle applied
to the sidebar restructure's delivery-note review queue. Every code
sample is written against files actually read during planning, not
guessed -- site-form-dialog.tsx, the /api/sites routes, matchSite,
po-fulfilment.ts, and the live purchase_orders.supplier data.
Self-review caught a real gap before committing: pb-bootstrap-schema.py
and pb-types.ts were named in the file-structure table but no task
actually touched them -- exactly the foot-gun docs/data-model.md
section 7 warns about. Added as Task 1 steps 5-8.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
19 -> ~16 was wrong. Ran the actual normalizeSupplierName() (trim +
whitespace-collapse + uppercase) against the three example pairs cited
as evidence: only the case-variant pair (Builders Warehouse) actually
collapses. The Ikhasi pair keeps a stray space after a hyphen that
differs between variants; the Global Precast pair differs by the
"(Pty) Ltd" suffix, not case or whitespace. Neither collapses under
exact-normalized matching.
This is not a defect -- it's the conservative-matching principle in
section 3 working as designed. The correct expectation against
production is 19 raw strings -> 18 groups, one merge. Both pairs that
don't collapse are exactly the kind of ambiguous case the spec already
says should stay separate for a human to resolve, not the kind that
should be forced together by loosening the normalizer.
Caught while grounding the implementation plan in real data, before
writing a single line of the plan itself.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Implements docs/superpowers/specs/2026-09-11-sidebar-restructure-design.md.
Reorganizes the flat 7-item sidebar into Workforce/Plant/Financial groups,
each with a landing dashboard and sub-pages split out of the former
combined Employees and Purchasing pages. Old routes (/dashboard/employees,
/fuel, /fuel/[vehicleId], /plant/[vehicleId], /purchasing,
/purchasing/[poId]) now redirect to their new locations so existing
bookmarks and links keep working.
New pages: Workforce, Plant, Financial landing dashboards; Timesheets
(entries table + upload, surfacing document status that was previously
invisible); Fuel Logs (flat table across both vehicle types); Delivery
Notes (the page docs/superpowers/specs/2026-09-11-ingestion-restructure-design.md
section 8 deliberately deferred); Uploads (combined PO/DN ingestion status).
Moved-unchanged pages (Workers, Vehicles, Equipment) verified
byte-identical to their prior content via direct diff against main --
relocated only, nothing altered.
Reviewed the initial implementation and found two issues, both fixed
here before commit:
- The delivery-note review queue (NeedsReviewDns) was duplicated onto
the Purchase Orders page instead of moving to the new Delivery Notes
page as specified -- same workflow rendered in two places from two
independent queries. Removed from Purchase Orders; it now lives
exclusively on Delivery Notes.
- The new Uploads page's timestamp column fell back to a `created`
field that doesn't exist on this PocketBase instance, so it could
never help. All 24 real purchase_orders predate date_uploaded being
backfilled and would have shown a blank column forever. Dropped the
dead fallback; the view already degrades to an em-dash for missing
timestamps.
Typecheck and full suite verified clean before and after both fixes
(583/583).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>