docs(sp4): per-claim detail drawer implementation plan (24 tasks, 5 phases)
This commit is contained in:
@@ -0,0 +1,712 @@
|
||||
# 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.
|
||||
|
||||
- [ ] **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**
|
||||
|
||||
```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
|
||||
|
||||
- [ ] **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**
|
||||
|
||||
```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.
|
||||
|
||||
- [ ] **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**
|
||||
|
||||
```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: <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**
|
||||
|
||||
```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`
|
||||
|
||||
- [ ] **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**
|
||||
|
||||
```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
|
||||
|
||||
- [ ] **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**
|
||||
|
||||
```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.
|
||||
|
||||
- [ ] **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`:
|
||||
|
||||
```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).
|
||||
|
||||
- [ ] **Step 1: Append the CSS variables + font import**
|
||||
- [ ] **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.
|
||||
|
||||
- [ ] **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**
|
||||
|
||||
```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
|
||||
|
||||
- [ ] **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**
|
||||
|
||||
```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
|
||||
|
||||
- [ ] **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**
|
||||
|
||||
```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
|
||||
|
||||
- [ ] **Step 1: Add failing tests** — empty, single line, multiple lines; procedure formatting
|
||||
- [ ] **Step 2: Implement**
|
||||
- [ ] **Step 3: Run tests, confirm green**
|
||||
- [ ] **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
|
||||
|
||||
- [ ] **Step 1: Add failing tests** — empty, single, multiple; qualifier shown when present
|
||||
- [ ] **Step 2: Implement**
|
||||
- [ ] **Step 3: Run tests, confirm green**
|
||||
- [ ] **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
|
||||
|
||||
- [ ] **Step 1: Add failing tests** — all 3 cards; missing fields gracefully omitted
|
||||
- [ ] **Step 2: Implement**
|
||||
- [ ] **Step 3: Run tests, confirm green**
|
||||
- [ ] **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 `<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**
|
||||
|
||||
```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)
|
||||
|
||||
- [ ] **Step 1: Add failing tests** — null returns nothing, populated renders all fields
|
||||
- [ ] **Step 2: Implement**
|
||||
- [ ] **Step 3: Run tests, confirm green**
|
||||
- [ ] **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
|
||||
|
||||
- [ ] **Step 1: Add failing tests** — empty, single, multiple; kind-color mapping
|
||||
- [ ] **Step 2: Implement**
|
||||
- [ ] **Step 3: Run tests, confirm green**
|
||||
- [ ] **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";
|
||||
```
|
||||
|
||||
- [ ] **Step 1: Create the barrel**
|
||||
- [ ] **Step 2: Run typecheck + tests, confirm green**
|
||||
- [ ] **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
|
||||
|
||||
- [ ] **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**
|
||||
|
||||
```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 `<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**
|
||||
|
||||
```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
|
||||
|
||||
- [ ] **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**
|
||||
|
||||
```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.
|
||||
|
||||
- [ ] **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**
|
||||
|
||||
```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`
|
||||
|
||||
- [ ] **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 |
|
||||
|
||||
## 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.
|
||||
Reference in New Issue
Block a user