Commit Graph

161 Commits

Author SHA1 Message Date
Tyler 7dd6a5d025 feat(drill): DrillableCell — hover-reveal button wrapper 2026-06-21 14:28:49 -06:00
Tyler be93dbff72 feat(drill): DrillStackProvider — zustand-backed peek stack with 2-level cap 2026-06-21 14:28:49 -06:00
Cyclone UI feb15bf48d Refine Acks page with hybrid dark/paper treatment
- Replace PageHeader with editorial hero: massive 'Acknowledgments, *received.*'
  (clamp 48-80px serif, italic) + ghost '999' watermark + ShieldCheck pill
  + ?-shortcut kbd hint
- Add torn-page fold with '↘ 999 REGISTER ↙' label and 48-dot perforation
- Cream paper plane (max-w-[1280px] mx-auto) with paper-grain SVG and
  double-bordered title block: 'REGISTER · 999 IMPLEMENTATION ACKS' over
  'The register.' (clamp 48-96px) plus on-file count column
- Folio system: § 01 Vital signs (4 paper KPI tiles via new AckKpiTile),
  § 02 The register (paper-toned Table with blue selected-row tint)
- Paper-toned AckCodeBadge (success/destructive/warning tints), paper-toned
  DownloadButton
- Error/Loading/Empty states wrapped in paper-toned containers
- Footer: 'End of register / Cyclone · 999 ACKs / N rows'

Round 9/11.
2026-06-21 14:27:16 -06:00
Tyler ff0985d244 Refine Inbox with editorial hero and paper-toned queue ledger
InboxHeader:
- Editorial dark hero replaces the 30px sticky header
- Massive 'Inbox.' (clamp 48-80px) with ghost amber 'TRIAGE' watermark
- Italic subtitle: 'Five lanes, one queue. N need eyes; N done today.'
- Right column: SUN JUN 21 / 14:15 / local
- Live status row: amber pulse + counts + date

Inbox page:
- Amber fold with '↘ FIVE LANES · ONE QUEUE ↙' between header and lanes
- Lanes stay dark (working surface preserved)
- New paper-toned Queue ledger panel below lanes:
  - Title block: 'The eye-flow, at a glance.' with need-eyes total
  - 5 paper tiles (Rejected / Payer rejected / Candidates / Unmatched / Done today)
  - Each tile shows lane accent dot + count + 'Clear'/'rows' chip
- Loading and error states now also use the new InboxHeader for consistency
- SummaryTile helper with paper-toned metric cards
2026-06-21 14:15:25 -06:00
Tyler 94990932f9 Refine Reconciliation page with hybrid dark/paper treatment
- Dark editorial hero: 'Manual pairing.' (80px serif, italic pairing)
- 'AUTO-MATCH PAUSED' amber status pill on the right
- Ghost 'PAIRED' watermark behind the hero
- Dramatic torn-page fold with shadow + perforation + 'PAIR THEM BY HAND' label
- Paper plane with title block ('RECONCILIATION · SHEET 01' / 'Pair them.' 96px)
- Folio system (§ 01 Vital signs, § 02 The match, § 03 The action)
- KPI strip with 3 paper-toned tiles (Unmatched claims / remits / in queue)
- Two-column pairing surface with paper cards, blue/amber accent rails
  and a center vertical bridge that flips to green-merge when both selected
- Active row uses paper-tinted blue (claim) or amber (remit) backgrounds
- Action footer with italic 'Ready to pair' / 'Now pick a remit' states
- Empty state: 'Nothing pending.' with circle-dashed icon on cream
- Footer: 'END OF SHEET 01 / CYCLONE · RECONCILIATION / N ROWS PENDING'
2026-06-21 14:05:05 -06:00
Tyler d0a18bd796 Refine Upload page with hybrid dark/paper treatment
- Dark editorial hero: 'Parse an X12 file.' (80px serif, italic X12)
- Ghost 'DROP' watermark behind the hero
- Dramatic torn-page fold with shadow + perforation + 'DROP & PARSE' label
- Paper plane with title block ('INGESTION · SHEET 01' / 'Drop a file.' 96px)
- Right column accepts indicator (.txt / 837P · 835 / backend status pill)
- Folio system (§ 01 The drop, § 02 The stream, § 03 The archive)
- Paper-toned drop zone, selects, and claim cards (replaces dark surface-2)
- Stream section with progress bar (gradient fill) and 2-column totals
- Recent batches list with 'batches' count in footer
- Empty state with inbox icon when no batches exist
- Footer: 'END OF SHEET 01 / CYCLONE · INGESTION / N BATCHES'
2026-06-21 13:55:22 -06:00
Tyler 5c9365ec33 feat(sp14): 5-lane Inbox UI with Payer-Rejected acknowledge action
Closes the gap between the SP10 backend (5 lanes) and the SP6
frontend (4 lanes). The Payer-Rejected lane (277CA STC A4/A6/A7)
is now rendered alongside Rejected/Candidates/Unmatched/Done,
with an Acknowledge bulk action that drops claims from the
working surface without erasing the original 277CA rejection
event (audit log stays intact, SP11).

Backend:
* Migration 0010: add payer_rejected_acknowledged_at +
  payer_rejected_acknowledged_actor columns + partial index.
* db.py: surface the two new columns on the Claim model.
* inbox_lanes.py: filter acknowledged claims out of the
  payer_rejected lane; expose the new fields on the row payload
  for forward-compat (e.g. a future 'Recently acknowledged' view).
* api.py:
  - POST /api/inbox/payer-rejected/acknowledge
    Bulk-acknowledge. Idempotent. Returns transitioned /
    already_acked / not_found / not_rejected counts so the UI
    can show '3 of 5 were already acknowledged' on a noop bulk.
    Writes a 'claim.payer_rejected_acknowledged' event to the
    SP11 hash-chained audit log.
  - GET /api/inbox/export.csv: accept 'payer_rejected' lane.
* test_acks.py: bump user_version assertion to 10.
* test_lane_filter_acknowledged.py: 4 tests for the lane filter
  and forward-compat row payload.
* test_payer_rejected_acknowledge.py: 6 tests for the endpoint
  (happy path, idempotency, no-op on non-rejected, missing
  ids, 400 on empty, audit-log wiring + chain integrity).

Frontend:
* lib/inbox-api.ts: add payer_rejected to InboxLanes, add
  acknowledgePayerRejected(), update exportInboxCsvUrl union.
* hooks/useInboxLanes.ts: add payer_rejected to initial state.
* hooks/useInboxLanes.test.ts: add payer_rejected to mocks.
* components/inbox/BulkBar.tsx: add 'payer_rejected' lane with
  Acknowledge action (no Resubmit, no Dismiss — payer-rejected
  is not eligible for either).
* components/inbox/BulkBar.test.tsx: add payer_rejected test.
* pages/Inbox.tsx: render the 5th lane, hook up onAcknowledge,
  include payer_rejected in the needEyes count.
* pages/Inbox.test.tsx: 3 new tests (5-lane render, need-eyes
  count, acknowledge action hits the right endpoint).
* components/inbox/InboxHeader.tsx: doc comment now explains
  why payer_rejected rolls up into need-eyes.

Pre-existing typecheck warnings in BulkBar.test.tsx / InboxRow
.test.tsx / Lane.tsx / download.test.ts are unchanged from
main — not touched here.

Test counts: backend 724 -> 734 (+10). Frontend 350 -> 354 (+4).
2026-06-21 00:13:47 -06:00
Tyler fbe9940a3f feat(ui): cohesive frontend polish — design system + per-screen refinement
Distill the UI into a single, recognizable voice: a precision
instrument for one operator on one machine. Bloomberg-coded chrome,
warm-paper detail surfaces, mono-heavy numerics, with one editorial
serif accent for moments of weight.

Design system
- Three-font stack: Geist Sans (UI), Geist Mono (data), Instrument
  Serif (editorial display). No Inter, no system fonts.
- Two surfaces: dark chrome (--background, --accent, --signal) and
  warm paper detail surfaces (--surface, --surface-ink*).
- Inbox has its own Ticker Tape terminal palette (--tt-*).
- Shared component classes: .eyebrow, .mono, .display, .surface,
  .surface-2, .row-hover, .nav-active, .kbd, .editorial, .hairline.
- --m-* token aliases for the legacy drawer components so test-
  asserted class strings keep resolving to the same hue family.

Drawers (Claim + Remit)
- Editorial display face for totals (paid/adjustment amounts).
- Color-coded money tiles: green-tinted paid card, amber-tinted
  adjustment card when non-zero, muted otherwise.
- Tabs get accent-blue underline + CSS-driven active state.
- Validation banners with proper background opacity + ring-around-
  dot success badge.
- StateHistoryTimeline: dashed border, ring around dots, ↳ prefix
  for remit ids.
- DiagnosesList, PartiesGrid, MatchedRemitCard, CasAdjustmentsPanel:
  refined typography, dashed dividers, italic descriptions, mono
  amounts, font-semibold totals, hover row tints.

Inbox Ticker Tape
- Custom RowCheckbox with sr-only input + amber accent.
- Alternating row striping, hover tints, accent rail with inset
  shadow on selection.
- Refined sparkline with glow at high values.
- BulkBar: bottom-floating bar with amber count chip, larger shadow.
- CandidateBreakdown: animated progress bars with amber gradient.
- InboxHeader: Instrument Serif headline, mono day/date stamp,
  pulsing amber status dot.

Dialogs & search
- NewClaimDialog: editorial title, mono NPI/CPT/amount fields.
- SearchBar: refined input row with mono, footer with pulsing
  loading indicator.
- KeyboardCheatsheet: monogram icon chip, hover row states, refined
  eyebrow header.

Primitives
- StatusBadge / ClaimStateBadge: per-state dot indicators.
- SelectItem: data-[highlighted]:outline tokens for keyboard a11y.
- Table primitives: refined header treatment, hover/focus states.

Tests + build
- 354/354 tests passing across 59 files.
- Vite build clean (53.84 kB CSS / 560.86 kB JS).
- Eyebrow assertions updated to match the consolidated .eyebrow
  class (intact visual contract, abstracted class string).
- Badge variant tokens updated to the polished bg-muted/80 /
  /0.14 opacity scale.
2026-06-20 22:27:01 -06:00
Tyler cb74bc9307 test(sp8): add coverage for resubmitRejectedWithDownload + bundle modal flow
- inbox-api.test.ts: pin ?download=true POST contract; blob + filename +
  X-Cyclone-Serialize-Errors parsing; non-2xx error surfacing.
- Inbox.test.tsx: end-to-end multi-select → Resubmit + Download path
  verifies api call args and downloadBlob wiring.
- inbox-api.ts: drop redundant isConfigured short-circuit in download
  variant (backend has its own auth gate) and switch error reading to
  res.text() to match Blob response shape.
2026-06-20 20:53:58 -06:00
Tyler bfcb0b3f38 feat(sp8): frontend — resubmit ZIP download UX
Wire the new ``?download=true`` resubmit endpoint into the Inbox
page. Operators can now ask the backend for a ZIP of regenerated
837s straight from the rejected-claims bulk action, with the
``X-Cyclone-Serialize-Errors`` header surfaced as a non-blocking
warning so partial successes don't swallow per-claim failures.

  * ``src/lib/inbox-api.ts``: new ``resubmitRejectedWithDownload``
    helper returning ``{blob, filename, serializeErrors}`` so callers
    can hand the bundle to the new ``downloadBlob`` utility without
    re-parsing headers.
  * ``src/lib/download.ts``: new ``downloadBlob(blob, filename)`` plus
    a test covering the extension/content-type mapping and the
    "use the suggested filename when present" rule.
  * ``src/pages/Inbox.tsx``: rejected-claims bulk action now exposes
    a "Resubmit & download" button next to the existing JSON path,
    wired through the helper. Conflicts and per-claim serialize
    errors render in the existing toast/result surface.

Tests: 4 new download.ts tests, 5 inbox-api tests (including
serialize-errors header parsing).
2026-06-20 20:49:58 -06:00
Tyler 1764df0cd5 feat(sp8): Claim drawer Download 837 button
Three pieces:

- src/lib/download.ts: generic downloadTextFile(filename, mime, text)
  helper. Mirrors csv.ts:downloadCsv but takes an explicit MIME type and
  drops the BOM prepend (which would corrupt the ISA segment).

- src/lib/api.ts: serializeClaim837(id) → {text, filename}. Fetches
  GET /api/claims/{id}/serialize-837, pulls the suggested filename from
  Content-Disposition (falls back to claim-{id}.x12 if the header is
  missing). Throws ApiError on non-2xx so callers can branch on .status.

- ClaimDrawerHeader: Download icon button between the amount and the
  close button. Click → api.serializeClaim837 → downloadTextFile.
  Disabled + 'Downloading 837 file' aria-label while the fetch is in
  flight so the click feels responsive. Optional onError prop surfaces
  fetch failures; defaults to a no-op so existing callers stay clean.

Tests: 3 download.test.ts, 3 api.test.ts, 2 header.test.ts (happy
path + error path). Frontend: 350 passing (+8 from 342).
2026-06-20 20:44:39 -06:00
Tyler b3de9c4d22 fix(sp7): mark ClaimDetail.lineReconciliation optional for back-compat
Existing test fixtures (ClaimDrawer, ClaimDrawerHeader, Claims pages)
construct partial ClaimDetail objects without the new slim
lineReconciliation projection. Other SP7 fields follow the optional
pattern (matchedLines?, serviceLinePayments?, claimLevelAdjustments?),
so apply the same to lineReconciliation.

Backend always emits it for matched claims; UI treats absent as
"no per-line audit yet".
2026-06-20 20:16:07 -06:00
Tyler 0b8f253186 feat(sp7): wire LineReconciliationTab into ClaimDrawer 2026-06-20 19:51:04 -06:00
Tyler 2569ff99a0 feat(sp7): LineReconciliationTab component 2026-06-20 19:47:49 -06:00
Tyler b538c0939b feat(sp7): MatchedRemitCard — line-count badge 2026-06-20 19:47:11 -06:00
Tyler 00cdd068fc feat(sp7): CasAdjustmentsPanel — per-line + claim-level sections 2026-06-20 19:46:30 -06:00
Tyler 77f61b675d feat(sp7): ServiceLinesTable — Paid + Adjustments columns 2026-06-20 19:45:35 -06:00
Tyler 2432ca0ec4 feat(sp7): inbox API types include matched/total line counts 2026-06-20 19:44:45 -06:00
Tyler 391a07f877 feat(sp7): add per-line reconciliation + matched-remittance types 2026-06-20 19:44:23 -06:00
Tyler fb8eba9bba feat(sp6): useInboxLanes refetches on SP5 tail events 2026-06-20 18:46:08 -06:00
Tyler 3157c86340 feat(sp6): bulk action bar wired into lanes 2026-06-20 18:43:51 -06:00
Tyler b607e4c459 feat(sp6): Inbox page + useInboxLanes hook 2026-06-20 18:41:44 -06:00
Tyler 610e580844 feat(sp6): InboxHeader + CandidateBreakdown 2026-06-20 18:41:07 -06:00
Tyler 5f3339ac1f feat(sp6): Lane component with multi-select 2026-06-20 18:40:34 -06:00
Tyler 713f0932b2 feat(sp6): InboxRow with Ticker Tape accents 2026-06-20 18:38:52 -06:00
Tyler ab841653d4 feat(sp6): /inbox route + sidebar nav link 2026-06-20 18:37:24 -06:00
Tyler bc34d991a9 feat(sp6): Ticker Tape color tokens for Inbox 2026-06-20 18:36:38 -06:00
Tyler f3e5d800e1 feat(sp6): inbox api client + types 2026-06-20 18:36:14 -06:00
Tyler 6ee87174d3 fix(activity): tolerate unknown activity kinds in ActivityFeed
The live-tail stream (now reachable through the new Vite /api proxy)
started delivering activity rows with kind="manual_match", a kind the
ActivityFeed kindConfig map didn't know about. The map lookup returned
undefined, and reading .icon on it threw — taking the whole Activity
Log page down.

Two fixes:

1. Add "manual_match" to the ActivityKind union in src/types/index.ts
   and to the kindConfig map in src/components/ActivityFeed.tsx (with
   a Wrench icon + neutral tone), so the kind is properly typed and
   rendered.

2. Add a FALLBACK_KIND guard so any future backend-emitted kind that
   arrives before the frontend is updated no longer crashes the page.
   kindConfig stays exhaustive for known kinds, so TypeScript still
   catches missing entries at compile time.

Also adds src/components/ActivityFeed.test.tsx with three regression
tests (empty state, multi-item render, unknown-kind safety).
2026-06-20 17:46:00 -06:00
Tyler f4a7cd99b3 merge: ui/batch-diff 2026-06-20 17:30:30 -06:00
Tyler 5baa0290f0 merge: ui/remit-drawer 2026-06-20 17:30:09 -06:00
Tyler 19fb675007 merge: ui/global-search 2026-06-20 17:29:45 -06:00
Tyler c94a1f8201 merge: ui/a11y-followup 2026-06-20 17:29:22 -06:00
Tyler e617d11fee merge: ui/print-styles 2026-06-20 17:29:20 -06:00
Tyler 7c4da65625 merge: ui/csv-export 2026-06-20 17:29:00 -06:00
Tyler 1359b0753a merge: SP5 live-tail — pub/sub + 3 stream endpoints + frontend tail integration
Brings the SP5 live-tail implementation into main:

Backend
  * EventBus (cyclone.pubsub): per-kind fan-out with drop-oldest overflow
  * FastAPI lifespan initializes the bus + db once per process
  * store.add() publishes claim_written / remittance_written /
    activity_recorded events on every batch write
  * GET /api/{claims,remittances,activity}/stream: NDJSON snapshot +
    live subscription + 15s idle heartbeat
  * EventBus.unsubscribe() lets the tail loop release its queue on
    client disconnect (no queue leak per open stream)

Frontend
  * src/lib/tail-stream.ts: streamTail() async-generator over fetch
  * src/store/tail-store.ts: zustand with FIFO cap 10k per slice
  * src/hooks/useTailStream.ts: connecting/live/reconnecting/stalled/error/closed
    state machine with 1→2→4→8→16→30s backoff
  * src/hooks/useMergedTail.ts: base + tail merge with filter
  * src/components/TailStatusPill.tsx: badge + Reconnect button
  * Claims, Remittances, ActivityLog pages wired to the tail

Tests
  * 437 backend tests pass (was 418 before SP5)
  * 154 frontend tests pass (was 124)
  * npm run typecheck clean
  * end-to-end smoke: open /api/claims/stream, POST 837, see new claims
    arrive in real time without refresh

# Conflicts:
#	src/pages/ActivityLog.tsx
#	src/pages/Remittances.test.tsx
#	src/pages/Remittances.tsx
2026-06-20 17:28:58 -06:00
Tyler a79668192d feat(frontend): global Cmd-K search across claims/remits/activity 2026-06-20 17:25:40 -06:00
Tyler c8d84c08c4 a11y(frontend): select outline, CardTitle polymorphic as prop, cheatsheet labels 2026-06-20 17:22:43 -06:00
Tyler 7b394fff1a feat(batch-diff): side-by-side claim diff between two batches 2026-06-20 17:17:09 -06:00
Tyler 25d47a5621 feat(frontend): RemitDrawer component with CAS adjustments + parties + claim payments 2026-06-20 17:16:23 -06:00
Tyler fa23eb182e feat(frontend): Remittances + ActivityLog wire live tail 2026-06-20 17:14:37 -06:00
Tyler 162127b494 feat(frontend): CSV export utility + ExportCsvButton component 2026-06-20 17:12:44 -06:00
Tyler 365e64e25a feat(frontend): Claims page wires live tail + TailStatusPill 2026-06-20 17:11:56 -06:00
Tyler 8523772792 style(frontend): print styles for detail drawers (Cmd-P paper records) 2026-06-20 17:09:37 -06:00
Tyler f1407dbb1d feat(frontend): TailStatusPill with reconnect button 2026-06-20 17:07:01 -06:00
Tyler e56a1eb503 feat(frontend): useMergedTail merges base + tail with filter 2026-06-20 17:05:24 -06:00
Tyler 0e766ce654 feat(frontend): useTailStream lifecycle hook with backoff + stall detection 2026-06-20 17:03:36 -06:00
Tyler 4cd52c3084 merge: ui/activity-filter — kind + since filters on ActivityLog 2026-06-20 17:00:07 -06:00
Tyler e76f821f22 merge: ui/keyboard-acks-remits — j/k row nav + ? cheatsheet on Acks and Remittances 2026-06-20 16:59:48 -06:00
Tyler d376647f5f merge: ui/batches-browser — list + detail drawer for /api/batches 2026-06-20 16:59:44 -06:00