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.py—get_claim_detail(claim_id) -> dict | Nonemethod onCycloneStorebackend/src/cyclone/api.py— newGET /api/claims/{claim_id}endpointbackend/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
Nonewhen claim not found - Reads
claims.raw_jsonand deserializes toClaimOutput(use the same_to_ui_claim_from_ormhelpers that already exist; build a new mapperto_ui_claim_detailthat returns the richer shape) - Queries
activity_eventsforclaim_id == X, ordered byts DESC, limit 50, maps to{kind, ts, batchId, remittanceId} - If
claims.matched_remittance_idis set, queries the remittance and embeds{id, totalPaid, status, receivedAt} - Use lazy
from cyclone import db as _dbimport inside the function (established pattern)
Tests (≥4):
test_get_claim_detail_returns_none_for_missingtest_get_claim_detail_happy_path_returns_full_shapetest_get_claim_detail_includes_state_historytest_get_claim_detail_includes_matched_remittance_summarytest_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_detailmapper +get_claim_detailmethod - 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 withClaimDetailJSON or 404 with{"error": "Not found", "detail": "..."}- Path param is
claim_id(notid) — 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_999endpoint pattern; use existing_claim_to_uihelpers) -
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
ClaimDetailinterface tosrc/types/index.ts(in the "backend-aligned" block, snake_case to match API) - Step 2: Add
api.getClaimDetail(id)method tosrc/lib/api.ts(usesapiFetchhelper 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 (checkpackage.json— if not present, use plainuseState+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
keydownlistener towindow(ordocument) whenenabled === 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 -
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=fromwindow.location.searchon mount; returns it asclaimId -
open(id)callshistory.pushStatewith?claim={id}(new history entry) -
setClaimId(id)callshistory.replaceState(no new history entry, but URL updates) -
close()strips the param viahistory.pushState -
Returns null
claimIdwhen 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.historyin tests via vitest'svi.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.cssonly 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):
Badgecomponent with color per state (submitted=neutral, matched=success, denied=error, etc.) -
Total amount (right of badge): big number, mono
-
Close button (top-right):
Buttonwith 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
rulefield 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.locationor 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-secondaryclaim_accepted,claim_paid→ success greenclaim_denied→ error redmanual_match,manual_unmatch→ warning amberremit_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
ClaimDrawerSkeletonwhile loading,ClaimDrawerErroron error, the section components on success -
onNext/onPrev: derives next/prev claim IDs from theclaimsprop (find current index, +1 or -1, wrap), then callsonNavigate(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=viauseDrawerUrlState() -
Render
<ClaimDrawer>whenclaimIdis 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
useStatefordrawerOpenor derive fromclaimId) -
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:
jnext,kprev,↑/↓same,escclose,?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:
- Start backend (
backend/.venv/bin/python -m cyclone serve &) - Start frontend (
npm run dev &) curl -s http://127.0.0.1:8000/api/health→ 200- Upload co_medicaid_837p.txt via
curl -F file=@...to/api/parse-837 curl -s http://127.0.0.1:8000/api/claims/{first-id}→ 200 + detail- 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
- Kill servers
- 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
git status— must be cleancd /Users/openclaw/dev/cyclone && git status— discard stale uncommitted changes if anygit merge --ff-only claim-drawer- Run
cd backend && .venv/bin/pytest -qfrom main — must show ~417 + 1 skip git worktree remove /Users/openclaw/dev/cyclone/.worktrees/claim-drawergit 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=Nparameter documented. - Keyboard nav collisions —
j/kwith browser find-in-page; documented in the cheatsheet. - Drawer + mobile — full-screen sheet on mobile; acceptable for desktop-first operator tool.
- URL sync race —
useDrawerUrlStatereads the URL on mount and triggers the fetch before caching can interfere.