From 683b6c8d0a768459bd4e17fe6c677202655e0038 Mon Sep 17 00:00:00 2001 From: SJCoetz Date: Fri, 10 Jul 2026 00:59:26 +0200 Subject: [PATCH] Add collection confirm rewrite spec --- ...07-09-collection-confirm-rewrite-design.md | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-09-collection-confirm-rewrite-design.md diff --git a/docs/superpowers/specs/2026-07-09-collection-confirm-rewrite-design.md b/docs/superpowers/specs/2026-07-09-collection-confirm-rewrite-design.md new file mode 100644 index 0000000..95c83e5 --- /dev/null +++ b/docs/superpowers/specs/2026-07-09-collection-confirm-rewrite-design.md @@ -0,0 +1,120 @@ +# Collection Confirm — layout rewrite + actor hydration fix + +**Goal:** Two distinct fixes on the Collection Confirm page: + +1. **Rewrite the row layout.** The current 3-column grid crams every field, action, attachment, invoice chip, and problem chip into a single row. Stacked vertical sections per row (header / details / actions / notes) read better, give the actions more room, and let the notes feed breathe. +2. **Fix the "actor id is empty" error.** The actor state hydrates from `localStorage.wfdash.actor`, which is never written. The real identity lives in the `wfdash_dev_email` cookie. Hydrate from the cookie via `getClientIdentity()` instead. + +## Scope + +### In Scope + +- Rewrite the `ConfirmCard` JSX in `src/app/(app)/collection-confirm/page.tsx` from the current 3-column grid to a stacked vertical layout. Sections: header (supplier + description + order/customer links) → details (ordered/collected/problem/invoice/attachments, two-column on `md:`) → actions (full-width buttons in a 2x3 grid on `sm:`, single column on `xs:`) → notes feed (existing). +- Replace the `localStorage.wfdash.actor` hydration with `getClientIdentity()` from `@/lib/dev-identity`. `actor.id = email`, `actor.name = email.split("@")[0]`. +- Add a tiny "Signed in as X" indicator in the page header (so the operator can see at a glance which identity is active, mirroring the dev-identity-switcher widget in the sprint page). +- Keep all existing behaviour: search, group by PO, bulk confirm, return to pool, all four dialogs (Quantity, Note, Attach, Confirm Invoice). + +### Out of Scope + +- Changes to the dev-identity switcher, dev-identity helpers, or the `wfdash_dev_email` cookie lifecycle. +- Changes to the `view-model.ts` (the page's existing helpers all stay). +- The `dev-identity-switcher` widget itself — it lives in the sprint page. We just borrow its style (a tiny header indicator) for consistency. +- Server routes — the actor flows entirely client-side via the cookie. + +## Current State + +- `ConfirmCard` (lines 504–752) uses `grid md:grid-cols-[minmax(0,1.2fr)_minmax(0,0.9fr)_auto]` to lay out header / details / actions side-by-side. The third column is a `flex flex-col` with five stacked buttons. On a typical 1440px screen the action column has ~12rem of width for five buttons + text — buttons truncate or wrap awkwardly. +- The `actor` state is hydrated from `localStorage.wfdash.actor` (lines 151–163). The dev-identity switcher writes to the `wfdash_dev_email` cookie via `setClientIdentity`, never to localStorage. The hydration finds nothing, `actor` stays empty, the submit handlers trip the "actor id is empty" toast. +- The sprint page (`src/app/(app)/sprint/page.tsx`) renders the `DevIdentitySwitcher` widget in its header. The collection-confirm page does not — so the operator has no way to see or change the active identity from this page. + +## User Confirmed Rule + +- Pick the best layout direction. (Option (b): stacked vertical sections per row, in a single card.) +- Actor id and name can be any shape derived from the cookie. (Use `id = email`, `name = local-part`.) + +## UX + +### Row card layout + +``` +┌─ Card ─────────────────────────────────────────────────────────┐ +│ [✓] Supplier name PO-2026-07-009 │ +│ Description text │ +│ 👤 Customer · 🏢 Company · 🔗 Order link │ +│ ───────────────────────────────────────────────────────────── │ +│ ORDERED 12 │ +│ COLLECTED 10 [Problem: short] [Invoice INV-1 · ...] │ +│ ATTACHMENTS 📎 proof.pdf 📎 photo.jpg │ +│ ───────────────────────────────────────────────────────────── │ +│ [ Adjust Quantity ] [ Add Note ] [ Attach ] │ +│ [ Confirm for invoicing ] [ Return to pool ] │ +│ ───────────────────────────────────────────────────────────── │ +│ Notes │ +│ • 2026-07-09 · david.els1 · QUANTITY │ +│ Adjusted qty 10 → 12. Operator error. │ +└────────────────────────────────────────────────────────────┘ +``` + +- The checkbox moves to the absolute top-left of the card, beside the supplier name. +- The actions grid is `grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2` — five buttons distribute cleanly. The primary "Confirm for invoicing" spans 2 columns on `sm:` to give it visual weight, the rest are 1 column each. +- The notes feed stays at the bottom, unchanged. +- The PO group header (the existing `
` above each group of rows) gets a `Selected: N` chip on the right and the "Confirm N of M" button. Unchanged otherwise. + +### Identity indicator in the page header + +A small, monospaced pill next to the "Refresh" button, mirroring the sprint page's identity switcher: + +``` +[ Refresh ] [ 👤 david.els1 ] +``` + +No edit affordance on this page — operators can change identity from the sprint page if needed. The pill is read-only here. + +## Behavior + +### Actor hydration + +Replace the localStorage effect with a call to `getClientIdentity()`: + +```ts +useEffect(() => { + if (typeof window === "undefined") return; + const email = getClientIdentity(); + if (!email) return; + setActor({ id: email, name: email.split("@")[0] }); +}, []); +``` + +The shape is unchanged: `actor.id` is the email (so `buildConfirmPatch`'s `setBy`/`setByName` fields stay valid), `actor.name` is the local-part (so badges and notes display the right operator name). + +If the cookie is unset, `getClientIdentity()` returns the `DEFAULT_DEV_EMAIL` (`david.els1@outlook.com`). The actor hydrates to that default, and the "actor id is empty" toast never fires. + +### Identity indicator + +A `useEffect` reads `getClientIdentity()` on mount and renders the local-part as a small pill. Updates only on full page reload (cookie is set elsewhere); no live updates needed. + +## Testing + +### Unit / component tests + +- `getClientIdentity()` returns the cookie value when set; falls back to `DEFAULT_DEV_EMAIL` otherwise. +- The page reads the actor from the cookie, not from localStorage. +- The "actor id is empty" toast does not fire when the cookie is set. + +The page itself has no test file today. The simplest test target is the hydration logic if we extract it into a helper, but that may be over-engineering. **Recommendation: add a tiny unit test for the new hydration effect via `@testing-library/react` for the `actor` state derivation.** + +If adding a test file is too heavy for this single change, leave it and document that the change is verified by manual smoke (open the page after a sprint-page identity switch; the cookie persists, the page hydrates, the confirm button works). + +## Acceptance Criteria + +- The row card reads top-to-bottom: header → details → actions → notes. No three-column cram. +- The Confirm for invoicing button is visually the primary action in the actions grid. +- The page header shows a "Signed in as X" pill that reflects the active dev identity. +- The "actor id is empty" toast never fires for a user who is signed in via the dev-identity switcher. +- All existing tests (if any) still pass; full suite non-regressive. + +## Risk / Rollback + +- **Layout risk:** every row's JSX changes. Visual regressions are likely in places I can't anticipate. The good news: there's no test on the page layout, so manual smoke is the only verification path. +- **Hydration risk:** low. The new effect reads the same cookie the rest of the app already uses. No new persistence surface. +- **Rollback:** revert the file. The actor hydration and layout changes are scoped to `page.tsx`. No shared component is touched. \ No newline at end of file