# Sub-project 4 — Per-Claim Detail Drawer: Implementation Plan **Date:** 2026-06-20 **Status:** Approved **Branch:** `claim-drawer` **Spec:** `docs/superpowers/specs/2026-06-20-cyclone-claim-drawer-design.md` 24 tasks across 5 phases. Follows the same task pattern as the SP3 plan (`[ ] Step 1 → Step N → commit`). TDD throughout. ## File structure **Backend additions (2 files):** - `backend/src/cyclone/store.py` — `get_claim_detail(claim_id) -> dict | None` method on `CycloneStore` - `backend/src/cyclone/api.py` — new `GET /api/claims/{claim_id}` endpoint - `backend/tests/test_api_claim_detail.py` — endpoint tests **Frontend additions (15 files):** ``` src/types/index.ts # +ClaimDetail interface src/lib/api.ts # +api.getClaimDetail(id) src/hooks/useClaimDetail.ts # fetch + cache src/hooks/useDrawerKeyboard.ts # j/k/esc handlers src/hooks/useDrawerUrlState.ts # ?claim= sync src/components/ClaimDrawer/ ├── ClaimDrawer.tsx # root, wires everything ├── ClaimDrawerHeader.tsx ├── ValidationPanel.tsx ├── ServiceLinesTable.tsx ├── DiagnosesList.tsx ├── PartiesGrid.tsx ├── RawSegmentsPanel.tsx ├── MatchedRemitCard.tsx ├── StateHistoryTimeline.tsx ├── ClaimDrawerSkeleton.tsx ├── ClaimDrawerError.tsx └── index.ts # barrel src/components/KeyboardCheatsheet/ # new ├── KeyboardCheatsheet.tsx └── index.ts src/pages/Claims.tsx # click handler + drawer render src/pages/Claims.test.tsx # +new tests src/index.css # +CSS variables for Modern palette ``` ## Phase 1 — Backend: `GET /api/claims/{id}` ### Task 1: `CycloneStore.get_claim_detail` method **Files:** - Modify: `backend/src/cyclone/store.py` - Create: `backend/tests/test_store_claim_detail.py` **Implementation notes:** - Returns `None` when claim not found - Reads `claims.raw_json` and deserializes to `ClaimOutput` (use the same `_to_ui_claim_from_orm` helpers that already exist; build a new mapper `to_ui_claim_detail` that returns the richer shape) - Queries `activity_events` for `claim_id == X`, ordered by `ts DESC`, limit 50, maps to `{kind, ts, batchId, remittanceId}` - If `claims.matched_remittance_id` is set, queries the remittance and embeds `{id, totalPaid, status, receivedAt}` - Use lazy `from cyclone import db as _db` import inside the function (established pattern) **Tests (≥4):** - `test_get_claim_detail_returns_none_for_missing` - `test_get_claim_detail_happy_path_returns_full_shape` - `test_get_claim_detail_includes_state_history` - `test_get_claim_detail_includes_matched_remittance_summary` - `test_get_claim_detail_no_matched_remittance_returns_null_field` TDD discipline: write tests first, confirm red, implement, confirm green. - [x] **Step 1: Add failing tests** in `backend/tests/test_store_claim_detail.py` - [x] **Step 2: Run tests to verify they fail** - [x] **Step 3: Implement `to_ui_claim_detail` mapper + `get_claim_detail` method** - [x] **Step 4: Run tests to verify they pass** - [x] **Step 5: Commit** ```bash git add backend/src/cyclone/store.py backend/tests/test_store_claim_detail.py git commit -m "feat(store): get_claim_detail returns full claim context with history + matched remit" ``` ### Task 2: `GET /api/claims/{claim_id}` endpoint **Files:** - Modify: `backend/src/cyclone/api.py` - Create: `backend/tests/test_api_claim_detail.py` **Endpoint behavior:** - `GET /api/claims/{claim_id}` — returns 200 with `ClaimDetail` JSON or 404 with `{"error": "Not found", "detail": "..."}` - Path param is `claim_id` (not `id`) — avoids shadowing FastAPI internals and matches `/api/acks/{ack_id}` convention from SP3 **Tests (≥4):** - `test_get_claim_detail_happy_path` — 200 + shape matches spec - `test_get_claim_detail_404_for_missing` — 404, not 500 - `test_get_claim_detail_state_history_includes_manual_match` — manual_match event appears in history - `test_get_claim_detail_matched_remit_inlines_summary` — when matched, summary fields are populated - [x] **Step 1: Add failing tests** in `backend/tests/test_api_claim_detail.py` - [x] **Step 2: Run tests to verify they fail** - [x] **Step 3: Add the endpoint** (mirror `parse_999` endpoint pattern; use existing `_claim_to_ui` helpers) - [x] **Step 4: Run tests to verify they pass** - [x] **Step 5: Commit** ```bash git add backend/src/cyclone/api.py backend/tests/test_api_claim_detail.py git commit -m "feat(api): GET /api/claims/{claim_id} returns full claim context" ``` ### Task 3: Phase 1 commit + full suite ```bash .venv/bin/pytest -q ``` Expected: 409 + ~8 new = ~417 pass + 1 skip. All green. - [x] **Step 1: Run the full backend suite, confirm green** - [x] **Step 2: Tag phase complete** (no commit unless changes; otherwise commit "test(sp4): phase 1 backend complete") ## Phase 2 — Frontend foundation ### Task 4: `ClaimDetail` TypeScript type + `api.getClaimDetail` **Files:** - Modify: `src/types/index.ts` - Modify: `src/lib/api.ts` - Modify: `src/lib/api.test.ts` The `ClaimDetail` type matches the JSON response shape from Task 2. Nested types: `Diagnosis`, `ServiceLine`, `Parties`, `Validation`, `MatchedRemittance`, `StateHistoryEvent`. - [x] **Step 1: Add `ClaimDetail` interface** to `src/types/index.ts` (in the "backend-aligned" block, snake_case to match API) - [x] **Step 2: Add `api.getClaimDetail(id)` method** to `src/lib/api.ts` (uses `apiFetch` helper if present) - [x] **Step 3: Add test** to `src/lib/api.test.ts` — happy path + 404 - [x] **Step 4: Run frontend tests + typecheck, confirm green** - [x] **Step 5: Commit** ```bash git add src/types/index.ts src/lib/api.ts src/lib/api.test.ts git commit -m "feat(frontend): ClaimDetail type + api.getClaimDetail" ``` ### Task 5: `useClaimDetail` hook **Files:** - Create: `src/hooks/useClaimDetail.ts` - Create: `src/hooks/useClaimDetail.test.ts` Hook signature: ```ts export function useClaimDetail(id: string | null): { data: ClaimDetail | null; isLoading: boolean; isError: boolean; error: Error | null; refetch: () => void; }; ``` Behavior: - When `id === null`, returns `{ data: null, isLoading: false, ... }` (drawer closed state) - Otherwise, fetches via `api.getClaimDetail(id)` using TanStack Query (check `package.json` — if not present, use plain `useState` + `useEffect`) - Caches for the drawer session so j/k navigation reuses prior fetches within ~30s - Returns 404 state as `{ isError: true, error: }` - [x] **Step 1: Add failing tests** — null id (no fetch), happy path, 404 - [x] **Step 2: Run tests, confirm red** - [x] **Step 3: Implement the hook** - [x] **Step 4: Run tests, confirm green** - [x] **Step 5: Commit** ```bash git add src/hooks/useClaimDetail.ts src/hooks/useClaimDetail.test.ts git commit -m "feat(frontend): useClaimDetail hook with null-id short-circuit" ``` ### Task 6: `useDrawerKeyboard` hook **Files:** - Create: `src/hooks/useDrawerKeyboard.ts` - Create: `src/hooks/useDrawerKeyboard.test.ts` Hook signature: ```ts export function useDrawerKeyboard(opts: { enabled: boolean; onNext: () => void; onPrev: () => void; onClose: () => void; onToggleHelp: () => void; }): void; ``` Behavior: - Attaches `keydown` listener to `window` (or `document`) when `enabled === true` - Maps `j` / `ArrowDown` → `onNext`, `k` / `ArrowUp` → `onPrev`, `Escape` → `onClose`, `?` (Shift+/) → `onToggleHelp` - Ignores keys when an input/textarea/contentEditable has focus - Cleans up listener on unmount or `enabled === false` - [x] **Step 1: Add failing tests** — fires onNext for "j", ignores when input focused, cleanup on unmount - [x] **Step 2: Run tests, confirm red** - [x] **Step 3: Implement the hook** - [x] **Step 4: Run tests, confirm green** - [x] **Step 5: Commit** ```bash git add src/hooks/useDrawerKeyboard.ts src/hooks/useDrawerKeyboard.test.ts git commit -m "feat(frontend): useDrawerKeyboard hook for j/k/esc/?" ``` ### Task 7: `useDrawerUrlState` hook **Files:** - Create: `src/hooks/useDrawerUrlState.ts` - Create: `src/hooks/useDrawerUrlState.test.ts` Hook signature: ```ts export function useDrawerUrlState(): { claimId: string | null; open: (id: string) => void; close: () => void; setClaimId: (id: string) => void; // for j/k nav }; ``` Behavior: - Reads `?claim=` from `window.location.search` on mount; returns it as `claimId` - `open(id)` calls `history.pushState` with `?claim={id}` (new history entry) - `setClaimId(id)` calls `history.replaceState` (no new history entry, but URL updates) - `close()` strips the param via `history.pushState` - Returns null `claimId` when no param is set - [x] **Step 1: Add failing tests** — reads URL on mount, open adds param, setClaimId replaces, close removes - [x] **Step 2: Run tests, confirm red** - [x] **Step 3: Implement the hook** (mock `window.history` in tests via vitest's `vi.stubGlobal`) - [x] **Step 4: Run tests, confirm green** - [x] **Step 5: Commit** ```bash git add src/hooks/useDrawerUrlState.ts src/hooks/useDrawerUrlState.test.ts git commit -m "feat(frontend): useDrawerUrlState hook for ?claim= sync" ``` ### Task 8: Phase 2 commit + typecheck ```bash npm run typecheck npm test -- --run ``` Expected: 10 + 5 new = 15 frontend pass. - [x] **Step 1: Run typecheck + frontend tests, confirm green** - [x] **Step 2: Commit if any changes** ## Phase 3 — Frontend leaf components > **Conventions for all components in this phase:** > - Use existing shadcn/ui primitives: `Sheet`, `Button`, `Badge`, `Skeleton` (already in project) > - Props are explicit, no context drilling > - Each component has its own test file > - Use Tailwind utility classes directly; add design tokens to `src/index.css` only when shadcn tokens don't fit ### Task 9: Add Modern palette CSS variables to `src/index.css` **Files:** - Modify: `src/index.css` Add the CSS variables from spec §3.3 to `:root`: ```css :root { /* Modern palette (SP4) */ --surface: #FAFAF7; --ink-primary: #0A0A0A; --ink-secondary: #6B6B6B; --ink-tertiary: #A0A0A0; --border-heavy: #1A1A1A; --accent: #1D4ED8; --accent-hover: #1E40AF; --error: #DC2626; --error-bg: #FEF2F2; --success: #15803D; --success-bg: #F0FDF4; --warning: #B45309; --warning-bg: #FFFBEB; } @media (prefers-color-scheme: dark) { /* Optional dark override — keep parity with existing token system. If existing shadcn dark tokens already cover dark mode, omit this block. */ } ``` Also add the Inter Tight + JetBrains Mono font imports (Google Fonts CDN). - [x] **Step 1: Append the CSS variables + font import** - [x] **Step 2: Commit** ```bash git add src/index.css git commit -m "feat(frontend): Modern palette CSS variables + Inter Tight/JetBrains Mono fonts" ``` ### Task 10: `ClaimDrawerSkeleton` + `ClaimDrawerError` (loading + error states) **Files:** - Create: `src/components/ClaimDrawer/ClaimDrawerSkeleton.tsx` - Create: `src/components/ClaimDrawer/ClaimDrawerError.tsx` - Create: `src/components/ClaimDrawer/ClaimDrawerSkeleton.test.tsx` - Create: `src/components/ClaimDrawer/ClaimDrawerError.test.tsx` **Skeleton:** Renders 8-12 `Skeleton` primitives matching section heights (header, validation, lines, dx, parties, history). No props. **Error:** Takes `{ kind: "not_found" | "network"; onRetry?: () => void; onClose: () => void }`. Renders the appropriate message + buttons. - [x] **Step 1: Add failing tests** for both components - [x] **Step 2: Implement** (keep simple — these are placeholder UIs) - [x] **Step 3: Run tests, confirm green** - [x] **Step 4: Commit** ```bash git add src/components/ClaimDrawer/ClaimDrawerSkeleton.tsx src/components/ClaimDrawer/ClaimDrawerSkeleton.test.tsx src/components/ClaimDrawer/ClaimDrawerError.tsx src/components/ClaimDrawer/ClaimDrawerError.test.tsx git commit -m "feat(frontend): ClaimDrawer skeleton + error states" ``` ### Task 11: `ClaimDrawerHeader` component **Files:** - Create: `src/components/ClaimDrawer/ClaimDrawerHeader.tsx` - Create: `src/components/ClaimDrawer/ClaimDrawerHeader.test.tsx` Props: `{ claim: ClaimDetail; onClose: () => void }` Renders: - Top-left: `Claim {id}` label (10px uppercase), then `{id}` (24px bold, mono) - State badge (right): `Badge` component with color per state (submitted=neutral, matched=success, denied=error, etc.) - Total amount (right of badge): big number, mono - Close button (top-right): `Button` with X icon - [x] **Step 1: Add failing tests** — renders ID, state badge with right color, total, close callback - [x] **Step 2: Implement** - [x] **Step 3: Run tests, confirm green** - [x] **Step 4: Commit** ```bash git add src/components/ClaimDrawer/ClaimDrawerHeader.tsx src/components/ClaimDrawer/ClaimDrawerHeader.test.tsx git commit -m "feat(frontend): ClaimDrawerHeader with state badge + close button" ``` ### Task 12: `ValidationPanel` component **Files:** - Create: `src/components/ClaimDrawer/ValidationPanel.tsx` - Create: `src/components/ClaimDrawer/ValidationPanel.test.tsx` Props: `{ validation: { passed: boolean; errors: ValidationIssue[]; warnings: ValidationIssue[] } }` Renders: - Section label `VALIDATION` - If passed and no warnings: small green checkmark + "All checks passed" - Otherwise: two sub-sections — Errors (red border-left) and Warnings (amber border-left) - Group errors by `rule` field with count (e.g., `R050_diagnosis_present (2)`) - List messages under each rule group - [x] **Step 1: Add failing tests** — empty state, errors only, warnings only, both, grouping - [x] **Step 2: Implement** - [x] **Step 3: Run tests, confirm green** - [x] **Step 4: Commit** ```bash git add src/components/ClaimDrawer/ValidationPanel.tsx src/components/ClaimDrawer/ValidationPanel.test.tsx git commit -m "feat(frontend): ValidationPanel with rule-grouped errors + warnings" ``` ### Task 13: `ServiceLinesTable` component **Files:** - Create: `src/components/ClaimDrawer/ServiceLinesTable.tsx` - Create: `src/components/ClaimDrawer/ServiceLinesTable.test.tsx` Props: `{ serviceLines: ServiceLine[] }` Renders: - Section label `SERVICE LINES ({n})` - Table: line #, procedure (qualifier + code + modifiers), charge (mono, big), units, date - Empty state: "No service lines" muted text - [x] **Step 1: Add failing tests** — empty, single line, multiple lines; procedure formatting - [x] **Step 2: Implement** - [x] **Step 3: Run tests, confirm green** - [x] **Step 4: Commit** ```bash git add src/components/ClaimDrawer/ServiceLinesTable.tsx src/components/ClaimDrawer/ServiceLinesTable.test.tsx git commit -m "feat(frontend): ServiceLinesTable with procedure + charge columns" ``` ### Task 14: `DiagnosesList` component **Files:** - Create: `src/components/ClaimDrawer/DiagnosesList.tsx` - Create: `src/components/ClaimDrawer/DiagnosesList.test.tsx` Props: `{ diagnoses: Diagnosis[] }` Renders: - Section label `DIAGNOSES ({n})` - Inline list of code (mono) + qualifier (when present) + description (when available via inline dict) - Empty state: "No diagnoses" muted text - [x] **Step 1: Add failing tests** — empty, single, multiple; qualifier shown when present - [x] **Step 2: Implement** - [x] **Step 3: Run tests, confirm green** - [x] **Step 4: Commit** ```bash git add src/components/ClaimDrawer/DiagnosesList.tsx src/components/ClaimDrawer/DiagnosesList.test.tsx git commit -m "feat(frontend): DiagnosesList with qualifier + code formatting" ``` ### Task 15: `PartiesGrid` component **Files:** - Create: `src/components/ClaimDrawer/PartiesGrid.tsx` - Create: `src/components/ClaimDrawer/PartiesGrid.test.tsx` Props: `{ parties: { billingProvider: ..., subscriber: ..., payer: ... } }` Renders: - Section label `PARTIES` - 3 stacked cards (vertical on mobile, 3-col grid on desktop ≥768px) - Each card: label (10px uppercase), name (16px), NPI/member ID (mono), address when present - [x] **Step 1: Add failing tests** — all 3 cards; missing fields gracefully omitted - [x] **Step 2: Implement** - [x] **Step 3: Run tests, confirm green** - [x] **Step 4: Commit** ```bash git add src/components/ClaimDrawer/PartiesGrid.tsx src/components/ClaimDrawer/PartiesGrid.test.tsx git commit -m "feat(frontend): PartiesGrid with billing provider/subscriber/payer cards" ``` ### Task 16: `RawSegmentsPanel` component **Files:** - Create: `src/components/ClaimDrawer/RawSegmentsPanel.tsx` - Create: `src/components/ClaimDrawer/RawSegmentsPanel.test.tsx` Props: `{ rawSegments: string[][] }` Renders: - Section label `RAW SEGMENTS ({n})` - Native `
` element (collapsed by default) - When expanded: monospace pre-formatted list with segments on separate lines, elements joined by `*` - [x] **Step 1: Add failing tests** — collapsed by default, toggle expands, segment rendering - [x] **Step 2: Implement** - [x] **Step 3: Run tests, confirm green** - [x] **Step 4: Commit** ```bash git add src/components/ClaimDrawer/RawSegmentsPanel.tsx src/components/ClaimDrawer/RawSegmentsPanel.test.tsx git commit -m "feat(frontend): RawSegmentsPanel collapsible debug aid" ``` ### Task 17: `MatchedRemitCard` component **Files:** - Create: `src/components/ClaimDrawer/MatchedRemitCard.tsx` - Create: `src/components/ClaimDrawer/MatchedRemitCard.test.tsx` Props: `{ matchedRemittance: MatchedRemittance | null }` Renders: - If null: returns null (no section) - Section label `MATCHED REMITTANCE` - Card with: remit ID, total paid (big mono), status badge, received date - "View remittance →" link button that opens the remittance detail (use `window.location` or a navigate handler — keep simple for v1: `window.location.href = \`/remittances?id=${matchedRemittance.id}\`` or trigger an event) - [x] **Step 1: Add failing tests** — null returns nothing, populated renders all fields - [x] **Step 2: Implement** - [x] **Step 3: Run tests, confirm green** - [x] **Step 4: Commit** ```bash git add src/components/ClaimDrawer/MatchedRemitCard.tsx src/components/ClaimDrawer/MatchedRemitCard.test.tsx git commit -m "feat(frontend): MatchedRemitCard with view-remittance link" ``` ### Task 18: `StateHistoryTimeline` component **Files:** - Create: `src/components/ClaimDrawer/StateHistoryTimeline.tsx` - Create: `src/components/ClaimDrawer/StateHistoryTimeline.test.tsx` Props: `{ history: StateHistoryEvent[] }` Renders: - Section label `STATE HISTORY ({n})` - Vertical timeline with a 2px left rule and colored dots per event kind: - `claim_submitted` → ink-secondary - `claim_accepted`, `claim_paid` → success green - `claim_denied` → error red - `manual_match`, `manual_unmatch` → warning amber - `remit_received` → accent blue - others → muted - Each row: kind (10px uppercase), ts (mono), optional remittance ID when present - Empty state: "No history events" muted - [x] **Step 1: Add failing tests** — empty, single, multiple; kind-color mapping - [x] **Step 2: Implement** - [x] **Step 3: Run tests, confirm green** - [x] **Step 4: Commit** ```bash git add src/components/ClaimDrawer/StateHistoryTimeline.tsx src/components/ClaimDrawer/StateHistoryTimeline.test.tsx git commit -m "feat(frontend): StateHistoryTimeline with kind-colored dots" ``` ### Task 19: Barrel export + phase 3 commit **Files:** - Create: `src/components/ClaimDrawer/index.ts` ```ts export { ClaimDrawer } from "./ClaimDrawer"; export { ClaimDrawerHeader } from "./ClaimDrawerHeader"; export { ValidationPanel } from "./ValidationPanel"; export { ServiceLinesTable } from "./ServiceLinesTable"; export { DiagnosesList } from "./DiagnosesList"; export { PartiesGrid } from "./PartiesGrid"; export { RawSegmentsPanel } from "./RawSegmentsPanel"; export { MatchedRemitCard } from "./MatchedRemitCard"; export { StateHistoryTimeline } from "./StateHistoryTimeline"; export { ClaimDrawerSkeleton } from "./ClaimDrawerSkeleton"; export { ClaimDrawerError } from "./ClaimDrawerError"; ``` - [x] **Step 1: Create the barrel** - [x] **Step 2: Run typecheck + tests, confirm green** - [x] **Step 3: Commit** ```bash git add src/components/ClaimDrawer/index.ts git commit -m "feat(frontend): ClaimDrawer barrel export" ``` ## Phase 4 — Drawer root + page wiring ### Task 20: `ClaimDrawer` root component **Files:** - Create: `src/components/ClaimDrawer/ClaimDrawer.tsx` - Create: `src/components/ClaimDrawer/ClaimDrawer.test.tsx` Props: ```ts { claimId: string | null; claims: { id: string }[]; // full list, used for j/k navigation onClose: () => void; onNavigate: (newId: string) => void; // called when user moves via j/k onToggleHelp: () => void; // called when ? pressed } ``` Uses shadcn `Sheet` (or `Dialog`) primitive. Behavior: - Renders nothing (or null) when `claimId === null` - Uses `useClaimDetail(claimId)` for data - Wires `useDrawerKeyboard({ enabled: !!claimId, onNext, onPrev, onClose, onToggleHelp })` - Renders `ClaimDrawerSkeleton` while loading, `ClaimDrawerError` on error, the section components on success - `onNext` / `onPrev`: derives next/prev claim IDs from the `claims` prop (find current index, +1 or -1, wrap), then calls `onNavigate(newId)` - `onToggleHelp`: passed through to the keyboard hook - [x] **Step 1: Add failing tests** — renders null when no claimId, skeleton on loading, error state on error, sections on success, j/k calls onNavigate with the correct next/prev id (including wrap), `?` calls onToggleHelp, ESC calls onClose - [x] **Step 2: Implement** — keep this component focused on orchestration; defer visual polish to leaf components - [x] **Step 3: Run tests, confirm green** - [x] **Step 4: Commit** ```bash git add src/components/ClaimDrawer/ClaimDrawer.tsx src/components/ClaimDrawer/ClaimDrawer.test.tsx git commit -m "feat(frontend): ClaimDrawer root orchestrator with skeleton/error/data states" ``` ### Task 21: `Claims.tsx` page wiring **Files:** - Modify: `src/pages/Claims.tsx` - Modify: `src/pages/Claims.test.tsx` Wire-up: - Read `?claim=` via `useDrawerUrlState()` - Render `` when `claimId` is non-null - Pass the full claim list as a sibling prop so j/k can navigate - Clicking a row sets the URL (via `open(id)` from the URL hook) - Clicking the dimmed overlay or ESC closes (via `close()`) - Background list dims to 60% opacity when drawer is open (use a `useState` for `drawerOpen` or derive from `claimId`) - [x] **Step 1: Add failing tests** — click row opens drawer; deep link opens on mount; ESC closes; URL clears - [x] **Step 2: Wire up the page** - [x] **Step 3: Run tests, confirm green** - [x] **Step 4: Commit** ```bash git add src/pages/Claims.tsx src/pages/Claims.test.tsx git commit -m "feat(frontend): Claims page wires click-to-open drawer with URL sync" ``` ### Task 22: `KeyboardCheatsheet` overlay **Files:** - Create: `src/components/KeyboardCheatsheet/KeyboardCheatsheet.tsx` - Create: `src/components/KeyboardCheatsheet/KeyboardCheatsheet.test.tsx` - Create: `src/components/KeyboardCheatsheet/index.ts` Props: `{ open: boolean; onClose: () => void }` Renders: - Centered modal-style overlay when open - Lists shortcuts: `j` next, `k` prev, `↑`/`↓` same, `esc` close, `?` toggle help - Dismisses on any other key or click outside - [x] **Step 1: Add failing tests** — renders when open, hidden otherwise, dismiss on close - [x] **Step 2: Implement** - [x] **Step 3: Run tests, confirm green** - [x] **Step 4: Commit** ```bash git add src/components/KeyboardCheatsheet/ git commit -m "feat(frontend): KeyboardCheatsheet overlay" ``` ### Task 23: Phase 4 commit + full suite ```bash npm test -- --run npm run typecheck .venv/bin/pytest -q ``` Expected: ~15 frontend, ~417 backend + 1 skip. All green. - [x] **Step 1: Run all checks** - [x] **Step 2: Commit if any changes** ## Phase 5 — Smoke + merge ### Task 24: End-to-end smoke Per spec §3.8: 1. Start backend (`backend/.venv/bin/python -m cyclone serve &`) 2. Start frontend (`npm run dev &`) 3. `curl -s http://127.0.0.1:8000/api/health` → 200 4. Upload co_medicaid_837p.txt via `curl -F file=@...` to `/api/parse-837` 5. `curl -s http://127.0.0.1:8000/api/claims/{first-id}` → 200 + detail 6. Visit `http://localhost:5173/claims?claim={id}` in a Playwright/Puppeteer-style headless browser, verify: - Drawer is open - All 8 sections render (header, validation, lines, dx, parties, raw, matched-remit if applicable, history) - Press `k` → URL updates, drawer updates - Press `esc` → URL clears, drawer closes 7. Kill servers 8. Commit empty: `git commit --allow-empty -m "smoke: end-to-end SP4 (claim drawer) flow passes"` - [x] **Step 1: Run the smoke** - [x] **Step 2: Commit** ### Task 25: README + plan doc updates **Files:** - Modify: `README.md` — mark "Sub-project 4" partially shipped (only per-claim drawer); describe the new feature - Modify: `docs/superpowers/plans/2026-06-20-cyclone-claim-drawer.md` — tick all `[ ]` to `[x]` - [x] **Step 1: Update README + plan doc** - [x] **Step 2: Commit** ```bash git add README.md docs/superpowers/plans/2026-06-20-cyclone-claim-drawer.md git commit -m "docs: mark SP4 partial shipped (claim drawer); tick all plan checkboxes" ``` ### Task 26: Cleanup + merge to main 1. `git status` — must be clean 2. `cd /Users/openclaw/dev/cyclone && git status` — discard stale uncommitted changes if any 3. `git merge --ff-only claim-drawer` 4. Run `cd backend && .venv/bin/pytest -q` from main — must show ~417 + 1 skip 5. `git worktree remove /Users/openclaw/dev/cyclone/.worktrees/claim-drawer` 6. `git branch -d claim-drawer` - [x] **Step 1: Verify worktree clean** - [x] **Step 2: Discard main's stale changes (if any)** - [x] **Step 3: Fast-forward merge to main** - [x] **Step 4: Remove worktree + delete branch** ## Test count targets | Phase | New backend | New frontend | Running total | |-------|-------------|--------------|---------------| | Start (post-SP3) | 409 + 1 skip | 10 | 419 / 10 | | P1 end | +8 | 0 | 417 / 10 | | P2 end | 0 | +5 | 417 / 15 | | P3 end | 0 | +10 (one per leaf) | 417 / 25 | | P4 end | 0 | +5 (drawer + page + cheatsheet) | 417 / 30 | | **Actual final** | **417 + 1 skip** | **124** | **417 / 124** | (Note: the frontend test count grew well past the conservative target — every leaf component ended up with its own dedicated test file, and the drawer root + Claims page wiring + keyboard cheatsheet each gained their own integration suite. Net new from SP4: 114 frontend tests across 14 files. Backend matched the +8 estimate exactly.) ## Risk reminders - Backend payload weight — cache aggressively in `useClaimDetail`. Hook returns a stable ref while data loads. - ActivityEvent volume — default limit 50; `?limit=N` parameter documented. - Keyboard nav collisions — `j`/`k` with browser find-in-page; documented in the cheatsheet. - Drawer + mobile — full-screen sheet on mobile; acceptable for desktop-first operator tool. - URL sync race — `useDrawerUrlState` reads the URL on mount and triggers the fetch before caching can interfere.