Files
cyclone/docs/superpowers/plans/2026-06-20-cyclone-claim-drawer.md

26 KiB

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.pyget_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.

  • Step 1: Add failing tests in backend/tests/test_store_claim_detail.py
  • Step 2: Run tests to verify they fail
  • Step 3: Implement to_ui_claim_detail mapper + get_claim_detail method
  • Step 4: Run tests to verify they pass
  • Step 5: Commit
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

  • Step 1: Add failing tests in backend/tests/test_api_claim_detail.py

  • Step 2: Run tests to verify they fail

  • Step 3: Add the endpoint (mirror parse_999 endpoint pattern; use existing _claim_to_ui helpers)

  • Step 4: Run tests to verify they pass

  • Step 5: Commit

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

.venv/bin/pytest -q

Expected: 409 + ~8 new = ~417 pass + 1 skip. All green.

  • Step 1: Run the full backend suite, confirm green
  • 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.

  • Step 1: Add ClaimDetail interface to src/types/index.ts (in the "backend-aligned" block, snake_case to match API)
  • Step 2: Add api.getClaimDetail(id) method to src/lib/api.ts (uses apiFetch helper if present)
  • Step 3: Add test to src/lib/api.test.ts — happy path + 404
  • Step 4: Run frontend tests + typecheck, confirm green
  • Step 5: Commit
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:

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: <NotFoundError> }

  • Step 1: Add failing tests — null id (no fetch), happy path, 404

  • Step 2: Run tests, confirm red

  • Step 3: Implement the hook

  • Step 4: Run tests, confirm green

  • Step 5: Commit

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:

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 / ArrowDownonNext, k / ArrowUponPrev, EscapeonClose, ? (Shift+/) → onToggleHelp

  • Ignores keys when an input/textarea/contentEditable has focus

  • Cleans up listener on unmount or enabled === false

  • Step 1: Add failing tests — fires onNext for "j", ignores when input focused, cleanup on unmount

  • Step 2: Run tests, confirm red

  • Step 3: Implement the hook

  • Step 4: Run tests, confirm green

  • Step 5: Commit

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:

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

  • Step 1: Add failing tests — reads URL on mount, open adds param, setClaimId replaces, close removes

  • Step 2: Run tests, confirm red

  • Step 3: Implement the hook (mock window.history in tests via vitest's vi.stubGlobal)

  • Step 4: Run tests, confirm green

  • Step 5: Commit

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

npm run typecheck
npm test -- --run

Expected: 10 + 5 new = 15 frontend pass.

  • Step 1: Run typecheck + frontend tests, confirm green
  • 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:

: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).

  • Step 1: Append the CSS variables + font import
  • Step 2: Commit
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.

  • Step 1: Add failing tests for both components
  • Step 2: Implement (keep simple — these are placeholder UIs)
  • Step 3: Run tests, confirm green
  • Step 4: Commit
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

  • Step 1: Add failing tests — renders ID, state badge with right color, total, close callback

  • Step 2: Implement

  • Step 3: Run tests, confirm green

  • Step 4: Commit

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

  • Step 1: Add failing tests — empty state, errors only, warnings only, both, grouping

  • Step 2: Implement

  • Step 3: Run tests, confirm green

  • Step 4: Commit

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

  • Step 1: Add failing tests — empty, single line, multiple lines; procedure formatting

  • Step 2: Implement

  • Step 3: Run tests, confirm green

  • Step 4: Commit

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

  • Step 1: Add failing tests — empty, single, multiple; qualifier shown when present

  • Step 2: Implement

  • Step 3: Run tests, confirm green

  • Step 4: Commit

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

  • Step 1: Add failing tests — all 3 cards; missing fields gracefully omitted

  • Step 2: Implement

  • Step 3: Run tests, confirm green

  • Step 4: Commit

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 <details> element (collapsed by default)

  • When expanded: monospace pre-formatted list with segments on separate lines, elements joined by *

  • Step 1: Add failing tests — collapsed by default, toggle expands, segment rendering

  • Step 2: Implement

  • Step 3: Run tests, confirm green

  • Step 4: Commit

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)

  • Step 1: Add failing tests — null returns nothing, populated renders all fields

  • Step 2: Implement

  • Step 3: Run tests, confirm green

  • Step 4: Commit

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

  • Step 1: Add failing tests — empty, single, multiple; kind-color mapping

  • Step 2: Implement

  • Step 3: Run tests, confirm green

  • Step 4: Commit

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
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";
  • Step 1: Create the barrel
  • Step 2: Run typecheck + tests, confirm green
  • Step 3: Commit
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:

{
  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

  • 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

  • Step 2: Implement — keep this component focused on orchestration; defer visual polish to leaf components

  • Step 3: Run tests, confirm green

  • Step 4: Commit

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 <ClaimDrawer> 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)

  • Step 1: Add failing tests — click row opens drawer; deep link opens on mount; ESC closes; URL clears

  • Step 2: Wire up the page

  • Step 3: Run tests, confirm green

  • Step 4: Commit

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

  • Step 1: Add failing tests — renders when open, hidden otherwise, dismiss on close

  • Step 2: Implement

  • Step 3: Run tests, confirm green

  • Step 4: Commit

git add src/components/KeyboardCheatsheet/
git commit -m "feat(frontend): KeyboardCheatsheet overlay"

Task 23: Phase 4 commit + full suite

npm test -- --run
npm run typecheck
.venv/bin/pytest -q

Expected: ~15 frontend, ~417 backend + 1 skip. All green.

  • Step 1: Run all checks
  • 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"
  • Step 1: Run the smoke
  • 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]

  • Step 1: Update README + plan doc

  • Step 2: Commit

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
  • Step 1: Verify worktree clean
  • Step 2: Discard main's stale changes (if any)
  • Step 3: Fast-forward merge to main
  • 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.