diff --git a/.planning/phases/05-finance-restyle/05-HUMAN-UAT.md b/.planning/phases/05-finance-restyle/05-HUMAN-UAT.md new file mode 100644 index 0000000..ea0a60e --- /dev/null +++ b/.planning/phases/05-finance-restyle/05-HUMAN-UAT.md @@ -0,0 +1,74 @@ +--- +status: partial +phase: 05-finance-restyle +source: [05-VERIFICATION.md] +started: 2026-05-03T00:00:00Z +updated: 2026-05-03T00:00:00Z +--- + +## Current Test + +[awaiting human testing — needs Docker rebuild first to make new code live] + +## Tests + +### 1. Skeleton on initial load +expected: | + Open `/mobile/finance` on iPhone 15 Pro (393×852). Before data arrives, FinanceSkeleton shows: 2×2 grid of skeleton tiles + 3-cell aging row + skeleton rows. Animation visible. +result: [pending] + +### 2. KPI 2×2 grid + Overdue accent +expected: | + Four cards: Total AR / Current / Overdue / Paid MTD. Currency rendered with zero decimals via fmt$. Overdue card has destructive (red) left border when value > 0. +result: [pending] + +### 3. No horizontal overflow at 360px +expected: | + Resize viewport to exactly 360px wide. No content scrolls horizontally. Currency values truncate or wrap rather than push the layout. +result: [pending] + +### 4. Open Invoices collapsible + tab filter +expected: | + Tap "Open Invoices" — Collapsible expands. Tab toggle "Open / Overdue" filters the list. Each row uses FinanceRow with `text-destructive` amount tone for overdue. +result: [pending] + +### 5. Recent Payments empty state + positive tone +expected: | + Tap "Recent Payments" — Collapsible expands. Payment amounts use `text-emerald-600` (positive tone). When list is empty, "—" em-dash renders. +result: [pending] + +### 6. No charts on Monthly Revenue +expected: | + Monthly Revenue section renders as a stacked list (Month → revenue → count rows). No bars, no canvas, no chart anywhere on the page. +result: [pending] + +### 7. QBO sync banner + toast messages +expected: | + Tap "Sync QBO" button. Sync banner appears with progress copy. On success: `toast.success("QuickBooks sync complete")` fires. On failure: `toast.error("Sync failed — check QBO connection")` fires. +result: [pending] + +### 8. Bottom nav active tab still works +expected: | + Bottom nav "Finance" tab uses `text-primary` when on this page. Tapping other tabs navigates correctly. (Phase 2 regression check.) +result: [pending] + +### 9. Error retry card +expected: | + Force a fetch error (block network). Page renders destructive-tinted card with "Failed to load finance data" + Retry button. Tapping Retry re-fetches. +result: [pending] + +### 10. Empty state — no outstanding AR +expected: | + When `total_ar === 0`, "No outstanding AR" message renders in place of the AR cards. (Verify against test data or in dev QBO.) +result: [pending] + +## Summary + +total: 10 +passed: 0 +issues: 0 +pending: 10 +skipped: 0 +blocked: 0 + +## Gaps diff --git a/.planning/phases/05-finance-restyle/05-VERIFICATION.md b/.planning/phases/05-finance-restyle/05-VERIFICATION.md new file mode 100644 index 0000000..ff23e25 --- /dev/null +++ b/.planning/phases/05-finance-restyle/05-VERIFICATION.md @@ -0,0 +1,207 @@ +--- +phase: 05-finance-restyle +verified: 2026-05-04T00:00:24Z +status: human_needed +score: 8/8 must-haves verified +human_verification: + - test: "Initial load skeleton — FinanceSkeleton renders for the loading window: 2x2 KPI tile skeletons + 3-cell aging row skeleton + two 3-row list skeletons. No flash of unstyled content. (D-17)" + expected: "Skeleton layout visible before data loads, no unstyled flash" + why_human: "Cannot observe skeleton timing or render order without a running browser" + - test: "KPI grid — 2x2 tiles render: Total AR / Current / Overdue (destructive left border via tone=attention) / Paid MTD. Paid MTD caption shows 'YTD: $X,XXX'. Currency renders with zero decimals. (D-01, D-02, D-05)" + expected: "Four tiles visible, Overdue has red left border, all amounts integer-formatted" + why_human: "Visual appearance of Card borders and typography scale requires viewport" + - test: "Aging row — visible only when overdue > 0. Three cells with amber/orange/destructive tones. No horizontal scroll at 360px. (D-08)" + expected: "3-cell grid fits without overflow at 360px width" + why_human: "Overflow at narrow viewport requires browser DevTools" + - test: "Open Invoices Collapsible — collapsed by default. Tap chevron to open. Tab toggle switches between Overdue and Current without refetch. Overdue amounts in text-destructive. (D-06, D-15, D-16)" + expected: "Collapsible opens/closes, tabs filter live, row amounts color correctly" + why_human: "Interaction behavior and color rendering require browser" + - test: "Recent Payments Collapsible — collapsed by default. Payment amounts in text-emerald-600. Empty payments body shows em-dash. (D-06, D-19)" + expected: "Payments open/close, amounts green, empty state shows dash" + why_human: "Interaction and color require browser" + - test: "Monthly Revenue — stacked list, NO chart. Each row: month left, revenue + invoice count right. No bars. (D-09)" + expected: "Divide-y list with no chart elements" + why_human: "Absence of chart bars vs legitimate layout elements requires visual check" + - test: "Sync flow — tap 'Sync QBO'. Banner appears with copy matching UI-SPEC ('Starting sync...' etc.). On success: sonner toast 'QuickBooks sync complete'. On failure: toast 'Sync failed — check QBO connection'. (D-14, D-17, D-18)" + expected: "Banner text matches spec, toasts fire on completion/failure" + why_human: "Live sync interaction with QBO requires running app" + - test: "No horizontal overflow at 360px viewport width — drag DevTools to 360px. No section overflows. (D-12)" + expected: "All sections fit within 360px; names truncate, grids wrap correctly" + why_human: "Overflow behavior requires viewport at exact 360px width" + - test: "No page H1 — shell HeaderBar (Wulf mark + Bell + avatar) is the only chrome above the KPI grid. (D-23)" + expected: "No 'Finance' heading rendered in page body" + why_human: "Absence of heading is best confirmed visually in context of full layout" + - test: "Bottom nav Finance tab active state — Finance icon shows text-primary when on /mobile/finance. (pre-existing Phase 2 behavior)" + expected: "Finance tab highlighted when on finance page" + why_human: "Active state detection requires running routing" +--- + +# Phase 5: Finance Restyle Verification Report + +**Phase Goal:** A manager reading AR / invoice / payment status on a phone sees properly spaced cards and stacked lists instead of squished wide tables — same data, new shell. +**Verified:** 2026-05-04T00:00:24Z +**Status:** human_needed +**Re-verification:** No — initial verification + +## Goal Achievement + +### Observable Truths + +| # | Truth | Status | Evidence | +|---|-------|--------|----------| +| 1 | 2-line stacked FinanceRow component rendered by both invoice rows and payment rows | ✓ VERIFIED | `FinanceRow.tsx` exists, exports `FinanceRow` + `FinanceRowProps`; `` used 2x in page.tsx (invoices + payments collapsibles) | +| 2 | FinanceSkeleton renders 4 KPI tile skeletons + 1 aging row + 2x 3-row list skeletons | ✓ VERIFIED | `FinanceSkeleton.tsx` confirmed: `grid-cols-2` (4x h-20), `grid-cols-3` (3x h-16), 2 sets of 3 h-4 row skeletons via local const | +| 3 | `/mobile/finance` opens directly to 4 KPI tiles (2x2 grid) using KpiCardMobile — no Finance H1 | ✓ VERIFIED | 8 ` 0 | ✓ VERIFIED | `text-amber-600`, `text-orange-600`, `text-destructive` present; gated on `summary.overdue_balance > 0` | +| 5 | Open Invoices and Recent Payments use shadcn Collapsible with FinanceRow rows in 2-line stacked layout | ✓ VERIFIED | ``, ``, `` present; ``; destructive retry card with "Failed to load finance data" + Retry button; `toast.success("QuickBooks sync complete")` and `toast.error(...)` both present | +| 8 | Empty state renders when total_ar === 0 AND open_invoices.length === 0 | ✓ VERIFIED | `isEmpty` computed; renders "No outstanding AR" card; KPI grid still renders in isEmpty branch | + +**Score:** 8/8 truths verified + +### Required Artifacts + +| Artifact | Expected | Status | Details | +|----------|----------|--------|---------| +| `components/mobile/FinanceRow.tsx` | Reusable 2-line stacked row (D-06) | ✓ VERIFIED | Exists; exports `FinanceRowProps` interface + `FinanceRow` function; `px-4 py-3`, `hover:bg-muted/50`, `text-sm font-semibold` line 1, `text-xs text-muted-foreground` line 2; `amountTone` supports destructive/positive/default; no priority stripe; no interactive handlers | +| `components/mobile/FinanceSkeleton.tsx` | Initial-load skeleton matching final layout (D-17) | ✓ VERIFIED | Exists; no-prop export; imports `Skeleton` from `@/components/ui/skeleton` (not Card); outer `px-4 py-4 space-y-6`; `grid-cols-2 gap-3` with `h-20`; `grid-cols-3 gap-2` with `h-16`; two 3-row list blocks with `h-4` | +| `app/mobile/finance/page.tsx` | Restyled mobile Finance page (FIN-01, FIN-02) | ✓ VERIFIED | Rewrote 309→391 lines; all 4 key imports present; KpiCardMobile (8x across both branches), FinanceRow (2x), FinanceSkeleton, Collapsible; no recharts; no raw red/yellow/green palette; no font-medium/font-bold; all locked decisions applied | + +### Key Link Verification + +| From | To | Via | Status | Details | +|------|----|-----|--------|---------| +| `app/mobile/finance/page.tsx` | `components/mobile/KpiCardMobile.tsx` | `import { KpiCardMobile }` | ✓ WIRED | Import + 8 JSX usages confirmed | +| `app/mobile/finance/page.tsx` | `components/mobile/FinanceRow.tsx` | `import { FinanceRow }` | ✓ WIRED | Import + 2 JSX usages (invoices + payments lists) | +| `app/mobile/finance/page.tsx` | `components/mobile/FinanceSkeleton.tsx` | `import { FinanceSkeleton }` | ✓ WIRED | Import + usage in `if (loading && !data)` branch | +| `app/mobile/finance/page.tsx` | `components/ui/collapsible.tsx` | `import { Collapsible, CollapsibleTrigger, CollapsibleContent }` | ✓ WIRED | Import + 2 Collapsible sections (invoices + payments) | +| `app/mobile/finance/page.tsx` | `/api/mobile/finance` | `fetch('/api/mobile/finance')` in `load()` | ✓ WIRED | Fetch present; `setData(await r.json())` wires response to state | +| `components/mobile/FinanceSkeleton.tsx` | `components/ui/skeleton.tsx` | `import { Skeleton }` | ✓ WIRED | Import confirmed; Skeleton used for all placeholder shapes | + +### Data-Flow Trace (Level 4) + +| Artifact | Data Variable | Source | Produces Real Data | Status | +|----------|--------------|--------|-------------------|--------| +| `app/mobile/finance/page.tsx` | `data` (FinanceData) | `fetch('/api/mobile/finance')` → `setData(await r.json())` | Yes — API route runs 6 parallel `postgresClient.query()` calls against `qbo_invoices`, `qbo_payments` | ✓ FLOWING | +| `app/api/mobile/finance/route.ts` | DB queries | `postgresClient.query(...)` x6 | Yes — SUM/COUNT aggregates from `qbo_invoices`, JOIN with `qbo_payments`, returns parsed numeric values | ✓ FLOWING | + +### Behavioral Spot-Checks + +Step 7b SKIPPED for visual UI components — output is rendered DOM, not testable via CLI without a running server and browser. + +### Requirements Coverage + +| Requirement | Source Plan | Description | Status | Evidence | +|-------------|------------|-------------|--------|----------| +| FIN-01 | 05-01, 05-02 | Page restyled with new Card and typography scale; spacing fixed for small phones | ✓ SATISFIED | KpiCardMobile 2x2 grid adopted; `text-sm`/`text-xs`/`text-[10px]` typography scale; `px-4` sections; `truncate` on names; `overflow-hidden` on containers; no raw color palette violations | +| FIN-02 | 05-01, 05-02 | Wide tables replaced with stacked lists; no new data, no new sections | ✓ SATISFIED | FinanceRow stacked rows (not ``); Top Customers stacked list with proportion bar; Monthly Revenue `divide-y` list; no new API routes; no new data sources; same `FinanceData` interface | + +### Anti-Patterns Found + +| File | Line | Pattern | Severity | Impact | +|------|------|---------|----------|--------| +| `components/mobile/KpiCardMobile.tsx` | 34 | `font-bold` on KPI value `

` | ℹ️ Info | Pre-existing Phase 3 component — NOT modified in Phase 5. D-03 constraint applies to Phase 5 new/modified files. KpiCardMobile was explicitly locked as "reuse, do not modify" (D-01). No action required. | + +No blockers found. The single informational note is a pre-existing Phase 3 pattern outside Phase 5 scope. + +### Locked Decisions Coverage (D-01 through D-23) + +| Decision | Honored | Evidence | +|----------|---------|----------| +| D-01: Card adoption for KPI tiles | ✓ | 4 `KpiCardMobile` tiles in 2x2 grid | +| D-02: KPI scale + Revenue YTD → Paid MTD caption | ✓ | `caption={\`YTD: ${fmt$(summary.paid_ytd)}\`}` | +| D-03: Two font weights only (semibold/normal) | ✓ | No `font-medium`, no `font-bold` in page.tsx, FinanceRow.tsx, FinanceSkeleton.tsx | +| D-04: Three size tiers | ✓ | `text-sm`, `text-xs`, `text-[10px]` throughout | +| D-05: fmt$() zero decimals | ✓ | `maximumFractionDigits: 0` preserved verbatim | +| D-06: Stacked 2-line rows via FinanceRow | ✓ | FinanceRow used for invoices + payments | +| D-07: Top customers stacked list | ✓ | Inline stacked list with proportion bar | +| D-08: Aging 3-cell locked palette | ✓ | amber-600, orange-600, text-destructive; no raw red/yellow | +| D-09: Monthly revenue as stacked list | ✓ | divide-y list; no recharts import | +| D-10: Page container spacing | ✓ | `pb-4 space-y-6` outer; each section owns `px-4` | +| D-11: Row internal spacing | ✓ | `gap-3`/`gap-2` in grids, `divide-y` between rows | +| D-12: No overflow at 360px | ? | Requires human verification | +| D-13: Section headers text-sm font-semibold | ✓ | `

` on all section headers | +| D-14: Header controls with aria-labels | ✓ | `aria-label="Refresh finance data"` + `aria-label="Sync from QuickBooks"` | +| D-15: Tab default overdue, no URL sync | ✓ | `useState<'open' \| 'overdue'>('overdue')`; no URL interaction | +| D-16: Collapsibles use shadcn Collapsible | ✓ | ``, ``, `` for both sections | +| D-17: Sync progress banner + FinanceSkeleton | ✓ | `if (loading && !data) return `; syncMsg banner with animate-spin | +| D-18: Error state destructive retry card | ✓ | Destructive-tinted card with "Failed to load finance data" + Retry button | +| D-19: Empty state + empty payments em-dash | ✓ | `isEmpty` gate renders "No outstanding AR"; `"—"` in empty payments body | +| D-20: API route unchanged | ✓ | `git diff --name-only HEAD app/api/mobile/finance/route.ts` returns 0 lines | +| D-21: FinanceData interface unchanged | ✓ | Interface preserved verbatim; toast.error in load() + syncAndRefresh() catch blocks | +| D-22: No new sections/sources | ✓ | Only `fetch('/api/mobile/finance')` and `fetch('/api/qbo/sync')` (pre-existing) | +| D-23: No page H1; layout.tsx untouched | ✓ | No `

Finance`; `git diff --name-only HEAD app/mobile/layout.tsx` returns 0 lines | + +### Regression Checks + +| Check | Status | Evidence | +|-------|--------|----------| +| `app/mobile/nav/` does NOT exist | ✓ PASS | `ls app/mobile/nav/` returns error (directory absent) | +| CLAUDE.md ~499 lines | ✓ PASS | `wc -l CLAUDE.md` returns 499 | +| Phase 1-4 components intact | ✓ PASS | BottomNav, HeaderBar, MoreDrawer, NeedsAttentionStrip, TicketFilterStrip, TicketRowSkeleton, WorkerStatusRow all present | +| `app/mobile/layout.tsx` unchanged | ✓ PASS | 0 lines in `git diff --name-only HEAD app/mobile/layout.tsx` | +| `components/mobile/KpiCardMobile.tsx` unchanged | ✓ PASS | Last commit on KpiCardMobile is Phase 4 restore (`9658640`) | + +### Human Verification Required + +The following items require a running browser at phone-width viewport to verify. All automated checks pass; these represent the visual/interactive contract that cannot be confirmed statically. + +#### 1. Skeleton loading state visual + +**Test:** Load `/mobile/finance` on a slow connection or with DB delayed. +**Expected:** FinanceSkeleton renders — 2x2 KPI tiles + 3-cell aging row + two 3-row list blocks. No flash of unstyled content. +**Why human:** Skeleton render timing and visual shape require browser observation. + +#### 2. KPI grid appearance + +**Test:** View the 4 KPI tiles on an iPhone 15 Pro (393px) and 360px width. +**Expected:** 2x2 grid; Overdue tile has red left border (`border-l-destructive`); all amounts integer-formatted (no decimals); Paid MTD caption shows "YTD: $X,XXX". +**Why human:** Card border color, grid layout, and caption formatting require viewport. + +#### 3. No horizontal overflow at 360px + +**Test:** Drag DevTools to 360px width. Scroll through all sections. +**Expected:** All sections fit within viewport — aging 3-column grid, customer names truncate, list rows wrap. +**Why human:** Overflow detection at exact 360px requires DevTools. + +#### 4. Open Invoices Collapsible interaction + +**Test:** Tap the Open Invoices collapsible header to open. Toggle between Overdue and Current tabs. +**Expected:** Collapsible opens/closes; tabs filter the list in place without refetch; overdue amounts render in red (text-destructive); tab line-indicator animates. +**Why human:** Interaction flow and color rendering require browser. + +#### 5. Recent Payments Collapsible + empty state + +**Test:** Tap the Recent Payments collapsible. +**Expected:** Opens to list of payments with amounts in emerald-600 (green). If no payments, shows "—" row. +**Why human:** emerald-600 vs other greens requires visual confirmation. + +#### 6. Monthly Revenue — no chart + +**Test:** Scroll to Monthly Revenue section. +**Expected:** Plain stacked list rows (month / revenue / count). No bars, no chart elements. +**Why human:** Absence of chart bars vs border-bottom dividers requires visual scan. + +#### 7. Sync flow + toast messages + +**Test:** Tap "Sync QBO" button. +**Expected:** Banner shows progression ("Starting sync…" → "Syncing with QuickBooks…" → "Refreshing data…"). On success: sonner toast "QuickBooks sync complete". Break connection, retry: toast "Sync failed — check QBO connection". +**Why human:** Live sync interaction requires running app + network manipulation. + +#### 8. Bottom nav Finance active tab + +**Test:** Navigate to `/mobile/finance`. +**Expected:** Finance (DollarSign) icon in bottom nav uses `text-primary`; other tabs use `text-muted-foreground`. +**Why human:** Active routing state and color rendering require browser. + +### Gaps Summary + +No gaps found. All 8 observable truths verified, all artifacts exist and are substantive, all key links are wired, data flows from DB through API to rendered components. The phase goal — "properly spaced cards and stacked lists instead of squished wide tables" — is fully implemented. + +The 10 human verification items above are residual interactive/visual checks that cannot be confirmed statically. They do not indicate missing implementation but rather runtime behaviors that require a browser to confirm. + +--- + +_Verified: 2026-05-04T00:00:24Z_ +_Verifier: Claude (gsd-verifier)_