docs: tick off completed boxes in sub-project 1 plan (housekeeping)

This commit is contained in:
Tyler
2026-06-20 00:11:01 -06:00
parent 6ffa5e8c72
commit b7f831e170
@@ -1,6 +1,6 @@
# Cyclone Production-Readiness (local-only) Implementation Plan # Cyclone Production-Readiness (local-only) Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking.
**Goal:** Add a backend in-memory batch store + 6 GET endpoints (with filter/sort/pagination and NDJSON streaming) to the Cyclone EDI suite, wire the existing 4 React pages + Upload page to live data via `@tanstack/react-query` v5, ship 4 fresh reference notes + a root README rewrite, and lock the local-only deploy posture (`127.0.0.1:8000`, no auth, no internet exposure). All on a single branch, with frequent commits, fully testable at every checkpoint. **Goal:** Add a backend in-memory batch store + 6 GET endpoints (with filter/sort/pagination and NDJSON streaming) to the Cyclone EDI suite, wire the existing 4 React pages + Upload page to live data via `@tanstack/react-query` v5, ship 4 fresh reference notes + a root README rewrite, and lock the local-only deploy posture (`127.0.0.1:8000`, no auth, no internet exposure). All on a single branch, with frequent commits, fully testable at every checkpoint.
@@ -84,7 +84,7 @@
- Create: `backend/src/cyclone/store.py` - Create: `backend/src/cyclone/store.py`
- Create: `backend/tests/test_store.py` - Create: `backend/tests/test_store.py`
- [ ] **Step 1: Write the failing test for `BatchRecord` construction + module import** - [x] **Step 1: Write the failing test for `BatchRecord` construction + module import**
`backend/tests/test_store.py`: `backend/tests/test_store.py`:
```python ```python
@@ -172,12 +172,12 @@ def test_get_missing_returns_none():
> Note: if your `ClaimOutput` / `Envelope` / `ValidationReport` / `ParseResult` model fields differ, adjust the `_make_claim` / `_make_result` stubs to match the real field names. Run `python -c "from cyclone.parsers.models import ClaimOutput; help(ClaimOutput)"` to confirm. > Note: if your `ClaimOutput` / `Envelope` / `ValidationReport` / `ParseResult` model fields differ, adjust the `_make_claim` / `_make_result` stubs to match the real field names. Run `python -c "from cyclone.parsers.models import ClaimOutput; help(ClaimOutput)"` to confirm.
- [ ] **Step 2: Run the test to verify it fails** - [x] **Step 2: Run the test to verify it fails**
Run: `cd backend && .venv/bin/pytest tests/test_store.py -v` Run: `cd backend && .venv/bin/pytest tests/test_store.py -v`
Expected: `ModuleNotFoundError: No module named 'cyclone.store'` (or similar import error). Expected: `ModuleNotFoundError: No module named 'cyclone.store'` (or similar import error).
- [ ] **Step 3: Implement `BatchRecord` + empty `InMemoryStore` (no mappers yet)** - [x] **Step 3: Implement `BatchRecord` + empty `InMemoryStore` (no mappers yet)**
`backend/src/cyclone/store.py`: `backend/src/cyclone/store.py`:
```python ```python
@@ -254,12 +254,12 @@ def utcnow_iso() -> str:
> Note: the imports `from cyclone.parsers.models import ParseResult` and `from cyclone.parsers.models_835 import ParseResult835` are correct based on the existing project layout. If the Pydantic v2 union complains about the discriminator, drop the `discriminator=None` and let Pydantic use the default behavior — the actual class is determined at runtime by the field type. > Note: the imports `from cyclone.parsers.models import ParseResult` and `from cyclone.parsers.models_835 import ParseResult835` are correct based on the existing project layout. If the Pydantic v2 union complains about the discriminator, drop the `discriminator=None` and let Pydantic use the default behavior — the actual class is determined at runtime by the field type.
- [ ] **Step 4: Run the tests to verify they pass** - [x] **Step 4: Run the tests to verify they pass**
Run: `cd backend && .venv/bin/pytest tests/test_store.py -v` Run: `cd backend && .venv/bin/pytest tests/test_store.py -v`
Expected: all 4 tests pass. Expected: all 4 tests pass.
- [ ] **Step 5: Commit** - [x] **Step 5: Commit**
```bash ```bash
git add backend/src/cyclone/store.py backend/tests/test_store.py git add backend/src/cyclone/store.py backend/tests/test_store.py
@@ -274,7 +274,7 @@ git commit -m "feat(backend): add cyclone.store with BatchRecord and InMemorySto
- Modify: `backend/src/cyclone/store.py` - Modify: `backend/src/cyclone/store.py`
- Modify: `backend/tests/test_store.py` - Modify: `backend/tests/test_store.py`
- [ ] **Step 1: Add failing tests for the mappers** - [x] **Step 1: Add failing tests for the mappers**
Append to `backend/tests/test_store.py`: Append to `backend/tests/test_store.py`:
```python ```python
@@ -359,12 +359,12 @@ def test_to_activity_event_uses_iso_timestamp():
> Adjust stub values to match your actual Pydantic model field names. The important assertions are the `status` mapping rules (submitted/pending/denied) and that the mapper output is a dict with the right shape. > Adjust stub values to match your actual Pydantic model field names. The important assertions are the `status` mapping rules (submitted/pending/denied) and that the mapper output is a dict with the right shape.
- [ ] **Step 2: Run the test to verify it fails** - [x] **Step 2: Run the test to verify it fails**
Run: `cd backend && .venv/bin/pytest tests/test_store.py -v` Run: `cd backend && .venv/bin/pytest tests/test_store.py -v`
Expected: `ImportError: cannot import name 'to_ui_claim' from 'cyclone.store'`. Expected: `ImportError: cannot import name 'to_ui_claim' from 'cyclone.store'`.
- [ ] **Step 3: Implement the mappers** - [x] **Step 3: Implement the mappers**
Add to `backend/src/cyclone/store.py` (after `utcnow_iso`): Add to `backend/src/cyclone/store.py` (after `utcnow_iso`):
```python ```python
@@ -509,12 +509,12 @@ def to_activity_event(
> from cyclone.parsers.models_835 import ClaimPayment, ParseResult835 > from cyclone.parsers.models_835 import ClaimPayment, ParseResult835
> ```) > ```)
- [ ] **Step 4: Run the tests to verify they pass** - [x] **Step 4: Run the tests to verify they pass**
Run: `cd backend && .venv/bin/pytest tests/test_store.py -v` Run: `cd backend && .venv/bin/pytest tests/test_store.py -v`
Expected: all 10 tests pass. Expected: all 10 tests pass.
- [ ] **Step 5: Commit** - [x] **Step 5: Commit**
```bash ```bash
git add backend/src/cyclone/store.py backend/tests/test_store.py git add backend/src/cyclone/store.py backend/tests/test_store.py
@@ -529,7 +529,7 @@ git commit -m "feat(backend): add store mappers (claim, remittance, provider, ac
- Modify: `backend/src/cyclone/store.py` - Modify: `backend/src/cyclone/store.py`
- Modify: `backend/tests/test_store.py` - Modify: `backend/tests/test_store.py`
- [ ] **Step 1: Add failing tests for the iterators** - [x] **Step 1: Add failing tests for the iterators**
Append to `backend/tests/test_store.py`: Append to `backend/tests/test_store.py`:
```python ```python
@@ -595,12 +595,12 @@ def test_recent_activity_returns_newest_first():
assert out[1]["timestamp"] == "2026-06-19T11:00:00Z" assert out[1]["timestamp"] == "2026-06-19T11:00:00Z"
``` ```
- [ ] **Step 2: Run tests to verify they fail** - [x] **Step 2: Run tests to verify they fail**
Run: `cd backend && .venv/bin/pytest tests/test_store.py -v -k "iter_ or distinct_ or recent_"` Run: `cd backend && .venv/bin/pytest tests/test_store.py -v -k "iter_ or distinct_ or recent_"`
Expected: `AttributeError: 'InMemoryStore' object has no attribute 'iter_claims'`. Expected: `AttributeError: 'InMemoryStore' object has no attribute 'iter_claims'`.
- [ ] **Step 3: Implement the iterators** - [x] **Step 3: Implement the iterators**
Add to `backend/src/cyclone/store.py` (as methods on `InMemoryStore`): Add to `backend/src/cyclone/store.py` (as methods on `InMemoryStore`):
```python ```python
@@ -724,12 +724,12 @@ Add to `backend/src/cyclone/store.py` (as methods on `InMemoryStore`):
return events[:limit] return events[:limit]
``` ```
- [ ] **Step 4: Run tests to verify they pass** - [x] **Step 4: Run tests to verify they pass**
Run: `cd backend && .venv/bin/pytest tests/test_store.py -v` Run: `cd backend && .venv/bin/pytest tests/test_store.py -v`
Expected: all tests pass (15 total now). Expected: all tests pass (15 total now).
- [ ] **Step 5: Commit** - [x] **Step 5: Commit**
```bash ```bash
git add backend/src/cyclone/store.py backend/tests/test_store.py git add backend/src/cyclone/store.py backend/tests/test_store.py
@@ -744,7 +744,7 @@ git commit -m "feat(backend): add store iterators (claims, remits, providers, ac
- Modify: `backend/src/cyclone/api.py` - Modify: `backend/src/cyclone/api.py`
- Create: `backend/tests/test_api_parse_persists.py` - Create: `backend/tests/test_api_parse_persists.py`
- [ ] **Step 1: Add failing tests** - [x] **Step 1: Add failing tests**
`backend/tests/test_api_parse_persists.py`: `backend/tests/test_api_parse_persists.py`:
```python ```python
@@ -807,12 +807,12 @@ def test_failed_parse_837_does_not_create_batch(client: TestClient):
assert len(global_store.list()) == 0 assert len(global_store.list()) == 0
``` ```
- [ ] **Step 2: Run tests to verify they fail** - [x] **Step 2: Run tests to verify they fail**
Run: `cd backend && .venv/bin/pytest tests/test_api_parse_persists.py -v` Run: `cd backend && .venv/bin/pytest tests/test_api_parse_persists.py -v`
Expected: first test fails because `len(global_store.list()) == 1` is False (no batches added yet). Expected: first test fails because `len(global_store.list()) == 1` is False (no batches added yet).
- [ ] **Step 3: Modify `cyclone.api` to call `store.add()` on success** - [x] **Step 3: Modify `cyclone.api` to call `store.add()` on success**
In `backend/src/cyclone/api.py`, add the import near the top: In `backend/src/cyclone/api.py`, add the import near the top:
```python ```python
@@ -853,12 +853,12 @@ import uuid
> The exact location depends on your existing parse-route structure. The key invariant is: `store.add(record)` is called only after the parser successfully produces a result, and *before* the response is returned. > The exact location depends on your existing parse-route structure. The key invariant is: `store.add(record)` is called only after the parser successfully produces a result, and *before* the response is returned.
- [ ] **Step 4: Run tests to verify they pass** - [x] **Step 4: Run tests to verify they pass**
Run: `cd backend && .venv/bin/pytest tests/test_api_parse_persists.py -v` Run: `cd backend && .venv/bin/pytest tests/test_api_parse_persists.py -v`
Expected: both tests pass. Expected: both tests pass.
- [ ] **Step 5: Commit** - [x] **Step 5: Commit**
```bash ```bash
git add backend/src/cyclone/api.py backend/tests/test_api_parse_persists.py git add backend/src/cyclone/api.py backend/tests/test_api_parse_persists.py
@@ -873,7 +873,7 @@ git commit -m "feat(backend): wire parse-837/parse-835 to persist to InMemorySto
- Modify: `backend/src/cyclone/api.py` - Modify: `backend/src/cyclone/api.py`
- Modify: `backend/tests/test_api_gets.py` (new) - Modify: `backend/tests/test_api_gets.py` (new)
- [ ] **Step 1: Add failing tests for the batch endpoints** - [x] **Step 1: Add failing tests for the batch endpoints**
`backend/tests/test_api_gets.py`: `backend/tests/test_api_gets.py`:
```python ```python
@@ -962,12 +962,12 @@ def test_get_batch_404_when_missing(client: TestClient):
assert resp.status_code == 404 assert resp.status_code == 404
``` ```
- [ ] **Step 2: Run tests to verify they fail** - [x] **Step 2: Run tests to verify they fail**
Run: `cd backend && .venv/bin/pytest tests/test_api_gets.py -v` Run: `cd backend && .venv/bin/pytest tests/test_api_gets.py -v`
Expected: all 4 tests fail with 404 (routes don't exist yet). Expected: all 4 tests fail with 404 (routes don't exist yet).
- [ ] **Step 3: Implement the routes** - [x] **Step 3: Implement the routes**
Add to `backend/src/cyclone/api.py`: Add to `backend/src/cyclone/api.py`:
```python ```python
@@ -1008,12 +1008,12 @@ def get_batch(batch_id: str):
return rec.result return rec.result
``` ```
- [ ] **Step 4: Run tests to verify they pass** - [x] **Step 4: Run tests to verify they pass**
Run: `cd backend && .venv/bin/pytest tests/test_api_gets.py -v -k "batches"` Run: `cd backend && .venv/bin/pytest tests/test_api_gets.py -v -k "batches"`
Expected: 4 tests pass. Expected: 4 tests pass.
- [ ] **Step 5: Commit** - [x] **Step 5: Commit**
```bash ```bash
git add backend/src/cyclone/api.py backend/tests/test_api_gets.py git add backend/src/cyclone/api.py backend/tests/test_api_gets.py
@@ -1028,7 +1028,7 @@ git commit -m "feat(backend): add GET /api/batches and GET /api/batches/{id}"
- Modify: `backend/src/cyclone/api.py` - Modify: `backend/src/cyclone/api.py`
- Modify: `backend/tests/test_api_gets.py` - Modify: `backend/tests/test_api_gets.py`
- [ ] **Step 1: Add failing tests** - [x] **Step 1: Add failing tests**
Append to `backend/tests/test_api_gets.py`: Append to `backend/tests/test_api_gets.py`:
```python ```python
@@ -1076,12 +1076,12 @@ def test_claims_pagination_limit_offset(seeded_store):
assert body1["has_more"] is True assert body1["has_more"] is True
``` ```
- [ ] **Step 2: Run tests to verify they fail** - [x] **Step 2: Run tests to verify they fail**
Run: `cd backend && .venv/bin/pytest tests/test_api_gets.py -v -k "claims_"` Run: `cd backend && .venv/bin/pytest tests/test_api_gets.py -v -k "claims_"`
Expected: 404 / Not Found for `/api/claims`. Expected: 404 / Not Found for `/api/claims`.
- [ ] **Step 3: Implement the route** - [x] **Step 3: Implement the route**
Add to `backend/src/cyclone/api.py`: Add to `backend/src/cyclone/api.py`:
```python ```python
@@ -1115,12 +1115,12 @@ def list_claims(
} }
``` ```
- [ ] **Step 4: Run tests to verify they pass** - [x] **Step 4: Run tests to verify they pass**
Run: `cd backend && .venv/bin/pytest tests/test_api_gets.py -v -k "claims_"` Run: `cd backend && .venv/bin/pytest tests/test_api_gets.py -v -k "claims_"`
Expected: 5 tests pass. Expected: 5 tests pass.
- [ ] **Step 5: Commit** - [x] **Step 5: Commit**
```bash ```bash
git add backend/src/cyclone/api.py backend/tests/test_api_gets.py git add backend/src/cyclone/api.py backend/tests/test_api_gets.py
@@ -1135,7 +1135,7 @@ git commit -m "feat(backend): add GET /api/claims with filter/sort/pagination"
- Modify: `backend/src/cyclone/api.py` - Modify: `backend/src/cyclone/api.py`
- Modify: `backend/tests/test_api_gets.py` - Modify: `backend/tests/test_api_gets.py`
- [ ] **Step 1: Add failing tests** - [x] **Step 1: Add failing tests**
Append to `backend/tests/test_api_gets.py`: Append to `backend/tests/test_api_gets.py`:
```python ```python
@@ -1198,12 +1198,12 @@ def test_activity_returns_one_event_per_claim(seeded_store):
assert all(e["kind"] == "claim_submitted" for e in body["items"]) assert all(e["kind"] == "claim_submitted" for e in body["items"])
``` ```
- [ ] **Step 2: Run tests to verify they fail** - [x] **Step 2: Run tests to verify they fail**
Run: `cd backend && .venv/bin/pytest tests/test_api_gets.py -v -k "remittances_ or providers_ or activity_"` Run: `cd backend && .venv/bin/pytest tests/test_api_gets.py -v -k "remittances_ or providers_ or activity_"`
Expected: 6 tests fail with 404. Expected: 6 tests fail with 404.
- [ ] **Step 3: Implement the three routes** - [x] **Step 3: Implement the three routes**
Add to `backend/src/cyclone/api.py`: Add to `backend/src/cyclone/api.py`:
```python ```python
@@ -1269,12 +1269,12 @@ def list_activity(
} }
``` ```
- [ ] **Step 4: Run tests to verify they pass** - [x] **Step 4: Run tests to verify they pass**
Run: `cd backend && .venv/bin/pytest tests/test_api_gets.py -v -k "remittances_ or providers_ or activity_"` Run: `cd backend && .venv/bin/pytest tests/test_api_gets.py -v -k "remittances_ or providers_ or activity_"`
Expected: 6 tests pass. Expected: 6 tests pass.
- [ ] **Step 5: Commit** - [x] **Step 5: Commit**
```bash ```bash
git add backend/src/cyclone/api.py backend/tests/test_api_gets.py git add backend/src/cyclone/api.py backend/tests/test_api_gets.py
@@ -1289,7 +1289,7 @@ git commit -m "feat(backend): add GET /api/{remittances,providers,activity}"
- Modify: `backend/src/cyclone/api.py` - Modify: `backend/src/cyclone/api.py`
- Create: `backend/tests/test_api_streaming.py` - Create: `backend/tests/test_api_streaming.py`
- [ ] **Step 1: Add failing test** - [x] **Step 1: Add failing test**
`backend/tests/test_api_streaming.py`: `backend/tests/test_api_streaming.py`:
```python ```python
@@ -1346,12 +1346,12 @@ def test_claims_ndjson_returns_parseable_lines(seeded_client):
assert summary["data"]["has_more"] is False assert summary["data"]["has_more"] is False
``` ```
- [ ] **Step 2: Run test to verify it fails** - [x] **Step 2: Run test to verify it fails**
Run: `cd backend && .venv/bin/pytest tests/test_api_streaming.py -v` Run: `cd backend && .venv/bin/pytest tests/test_api_streaming.py -v`
Expected: response is JSON, not NDJSON — content-type assertion fails. Expected: response is JSON, not NDJSON — content-type assertion fails.
- [ ] **Step 3: Implement streaming on `/api/claims` (and apply the same pattern to remittances/providers/activity)** - [x] **Step 3: Implement streaming on `/api/claims` (and apply the same pattern to remittances/providers/activity)**
In `backend/src/cyclone/api.py`, refactor `list_claims` to content-negotiate: In `backend/src/cyclone/api.py`, refactor `list_claims` to content-negotiate:
@@ -1405,12 +1405,12 @@ def list_claims(
Apply the same pattern to `list_remittances`, `list_providers`, and `list_activity` (each branches on `request.headers.get("accept") == "application/x-ndjson"` and returns `StreamingResponse` when set). Apply the same pattern to `list_remittances`, `list_providers`, and `list_activity` (each branches on `request.headers.get("accept") == "application/x-ndjson"` and returns `StreamingResponse` when set).
- [ ] **Step 4: Run test to verify it passes** - [x] **Step 4: Run test to verify it passes**
Run: `cd backend && .venv/bin/pytest tests/test_api_streaming.py -v` Run: `cd backend && .venv/bin/pytest tests/test_api_streaming.py -v`
Expected: test passes. Expected: test passes.
- [ ] **Step 5: Commit** - [x] **Step 5: Commit**
```bash ```bash
git add backend/src/cyclone/api.py backend/tests/test_api_streaming.py git add backend/src/cyclone/api.py backend/tests/test_api_streaming.py
@@ -1424,7 +1424,7 @@ git commit -m "feat(backend): add NDJSON streaming on list endpoints"
**Files:** **Files:**
- Modify: `backend/src/cyclone/__main__.py` - Modify: `backend/src/cyclone/__main__.py`
- [ ] **Step 1: Update `__main__.py` to bind loopback and honor env vars** - [x] **Step 1: Update `__main__.py` to bind loopback and honor env vars**
Replace `backend/src/cyclone/__main__.py` with: Replace `backend/src/cyclone/__main__.py` with:
```python ```python
@@ -1470,7 +1470,7 @@ if __name__ == "__main__":
main() main()
``` ```
- [ ] **Step 2: Verify locally (manual)** - [x] **Step 2: Verify locally (manual)**
Run: `cd backend && .venv/bin/python -m cyclone serve &` Run: `cd backend && .venv/bin/python -m cyclone serve &`
Then: `curl -s http://127.0.0.1:8000/api/health | jq .` → should print `{"status": "ok", "version": "0.1.0"}`. Then: `curl -s http://127.0.0.1:8000/api/health | jq .` → should print `{"status": "ok", "version": "0.1.0"}`.
@@ -1478,7 +1478,7 @@ Then: `curl -s http://0.0.0.0:8000/api/health` → should fail (refused; loopbac
Kill the server: `kill %1` (or `pkill -f 'cyclone.api:app'`). Kill the server: `kill %1` (or `pkill -f 'cyclone.api:app'`).
- [ ] **Step 3: Commit** - [x] **Step 3: Commit**
```bash ```bash
git add backend/src/cyclone/__main__.py git add backend/src/cyclone/__main__.py
@@ -1489,12 +1489,12 @@ git commit -m "feat(backend): bind serve to 127.0.0.1:8000, honor CYCLONE_PORT/C
### Task 10: Run the full backend test suite + manual smoke ### Task 10: Run the full backend test suite + manual smoke
- [ ] **Step 1: Run all backend tests** - [x] **Step 1: Run all backend tests**
Run: `cd backend && .venv/bin/pytest -v` Run: `cd backend && .venv/bin/pytest -v`
Expected: all tests pass (existing 121 + new ~23 = 144+ total). Expected: all tests pass (existing 121 + new ~23 = 144+ total).
- [ ] **Step 2: Manual smoke (in two terminals)** - [x] **Step 2: Manual smoke (in two terminals)**
Terminal 1: `cd backend && .venv/bin/python -m cyclone serve` Terminal 1: `cd backend && .venv/bin/python -m cyclone serve`
Terminal 2: `curl -s -X POST -F "file=@backend/tests/fixtures/co_medicaid_837p.txt" -H "Accept: application/json" http://127.0.0.1:8000/api/parse-837 | jq .summary` Terminal 2: `curl -s -X POST -F "file=@backend/tests/fixtures/co_medicaid_837p.txt" -H "Accept: application/json" http://127.0.0.1:8000/api/parse-837 | jq .summary`
@@ -1502,7 +1502,7 @@ Then: `curl -s 'http://127.0.0.1:8000/api/claims?status=submitted' | jq '{total,
Then: `curl -s 'http://127.0.0.1:8000/api/claims' -H 'Accept: application/x-ndjson' | head -4` Then: `curl -s 'http://127.0.0.1:8000/api/claims' -H 'Accept: application/x-ndjson' | head -4`
Expected: parse succeeds; `/api/claims` returns 2 items; NDJSON returns 2 item lines + 1 summary line. Expected: parse succeeds; `/api/claims` returns 2 items; NDJSON returns 2 item lines + 1 summary line.
- [ ] **Step 3: Commit any leftover changes (likely none)** - [x] **Step 3: Commit any leftover changes (likely none)**
```bash ```bash
git status # should be clean git status # should be clean
@@ -1517,12 +1517,12 @@ git status # should be clean
**Files:** **Files:**
- Modify: `package.json` - Modify: `package.json`
- [ ] **Step 1: Add the dep** - [x] **Step 1: Add the dep**
Run: `cd /Users/openclaw/dev/cyclone && npm install @tanstack/react-query@^5.59.0` Run: `cd /Users/openclaw/dev/cyclone && npm install @tanstack/react-query@^5.59.0`
Expected: `package.json` updated; `node_modules` grows. Expected: `package.json` updated; `node_modules` grows.
- [ ] **Step 2: Commit** - [x] **Step 2: Commit**
```bash ```bash
git add package.json package-lock.json git add package.json package-lock.json
@@ -1536,7 +1536,7 @@ git commit -m "feat(frontend): add @tanstack/react-query ^5.59.0"
**Files:** **Files:**
- Modify: `src/main.tsx` - Modify: `src/main.tsx`
- [ ] **Step 1: Update `main.tsx`** - [x] **Step 1: Update `main.tsx`**
Replace `src/main.tsx` with: Replace `src/main.tsx` with:
```tsx ```tsx
@@ -1567,12 +1567,12 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
); );
``` ```
- [ ] **Step 2: Verify build still passes** - [x] **Step 2: Verify build still passes**
Run: `npm run build` Run: `npm run build`
Expected: build succeeds with no TS errors. Expected: build succeeds with no TS errors.
- [ ] **Step 3: Commit** - [x] **Step 3: Commit**
```bash ```bash
git add src/main.tsx git add src/main.tsx
@@ -1586,7 +1586,7 @@ git commit -m "feat(frontend): wrap App in QueryClientProvider"
**Files:** **Files:**
- Modify: `tailwind.config.js` - Modify: `tailwind.config.js`
- [ ] **Step 1: Add the keyframes** - [x] **Step 1: Add the keyframes**
In `tailwind.config.js`, inside `theme.extend.keyframes`, add three new entries (next to the existing `fade-in`): In `tailwind.config.js`, inside `theme.extend.keyframes`, add three new entries (next to the existing `fade-in`):
```js ```js
@@ -1605,7 +1605,7 @@ In `tailwind.config.js`, inside `theme.extend.keyframes`, add three new entries
}, },
``` ```
- [ ] **Step 2: Commit** - [x] **Step 2: Commit**
```bash ```bash
git add tailwind.config.js git add tailwind.config.js
@@ -1619,7 +1619,7 @@ git commit -m "feat(frontend): add shimmer, scan, row-flash keyframes"
**Files:** **Files:**
- Create: `src/components/ui/skeleton.tsx` - Create: `src/components/ui/skeleton.tsx`
- [ ] **Step 1: Create the file** - [x] **Step 1: Create the file**
`src/components/ui/skeleton.tsx`: `src/components/ui/skeleton.tsx`:
```tsx ```tsx
@@ -1679,12 +1679,12 @@ export function Skeleton({ className, variant = "row" }: SkeletonProps) {
} }
``` ```
- [ ] **Step 2: Verify build** - [x] **Step 2: Verify build**
Run: `npm run build` Run: `npm run build`
Expected: passes. Expected: passes.
- [ ] **Step 3: Commit** - [x] **Step 3: Commit**
```bash ```bash
git add src/components/ui/skeleton.tsx git add src/components/ui/skeleton.tsx
@@ -1698,7 +1698,7 @@ git commit -m "feat(frontend): add Skeleton UI primitive (hairline + scanline-sh
**Files:** **Files:**
- Create: `src/components/ui/empty-state.tsx` - Create: `src/components/ui/empty-state.tsx`
- [ ] **Step 1: Create the file** - [x] **Step 1: Create the file**
`src/components/ui/empty-state.tsx`: `src/components/ui/empty-state.tsx`:
```tsx ```tsx
@@ -1726,7 +1726,7 @@ export function EmptyState({ eyebrow, message }: EmptyStateProps) {
} }
``` ```
- [ ] **Step 2: Verify build + commit** - [x] **Step 2: Verify build + commit**
```bash ```bash
npm run build npm run build
@@ -1741,7 +1741,7 @@ git commit -m "feat(frontend): add EmptyState primitive (instrument-label eyebro
**Files:** **Files:**
- Create: `src/components/ui/error-state.tsx` - Create: `src/components/ui/error-state.tsx`
- [ ] **Step 1: Create the file** - [x] **Step 1: Create the file**
`src/components/ui/error-state.tsx`: `src/components/ui/error-state.tsx`:
```tsx ```tsx
@@ -1776,7 +1776,7 @@ export function ErrorState({ error, onRetry }: ErrorStateProps) {
} }
``` ```
- [ ] **Step 2: Verify build + commit** - [x] **Step 2: Verify build + commit**
```bash ```bash
npm run build npm run build
@@ -1791,7 +1791,7 @@ git commit -m "feat(frontend): add ErrorState primitive (destructive hairline +
**Files:** **Files:**
- Create: `src/components/ui/filter-chips.tsx` - Create: `src/components/ui/filter-chips.tsx`
- [ ] **Step 1: Create the file** - [x] **Step 1: Create the file**
`src/components/ui/filter-chips.tsx`: `src/components/ui/filter-chips.tsx`:
```tsx ```tsx
@@ -1847,7 +1847,7 @@ export function FilterChips({
} }
``` ```
- [ ] **Step 2: Verify build + commit** - [x] **Step 2: Verify build + commit**
```bash ```bash
npm run build npm run build
@@ -1862,7 +1862,7 @@ git commit -m "feat(frontend): add FilterChips primitive (read-only pill row)"
**Files:** **Files:**
- Create: `src/components/ui/pagination.tsx` - Create: `src/components/ui/pagination.tsx`
- [ ] **Step 1: Create the file** - [x] **Step 1: Create the file**
`src/components/ui/pagination.tsx`: `src/components/ui/pagination.tsx`:
```tsx ```tsx
@@ -1951,7 +1951,7 @@ export function Pagination({ page, pageSize, total, onChange }: PaginationProps)
} }
``` ```
- [ ] **Step 2: Verify build + commit** - [x] **Step 2: Verify build + commit**
```bash ```bash
npm run build npm run build
@@ -1966,7 +1966,7 @@ git commit -m "feat(frontend): add Pagination primitive (nav-active echo, num pa
**Files:** **Files:**
- Modify: `src/components/Layout.tsx` - Modify: `src/components/Layout.tsx`
- [ ] **Step 1: Update `Layout.tsx`** - [x] **Step 1: Update `Layout.tsx`**
Replace `src/components/Layout.tsx` with: Replace `src/components/Layout.tsx` with:
```tsx ```tsx
@@ -2000,7 +2000,7 @@ export function Layout() {
} }
``` ```
- [ ] **Step 2: Verify build + commit** - [x] **Step 2: Verify build + commit**
```bash ```bash
npm run build npm run build
@@ -2016,7 +2016,7 @@ git commit -m "feat(frontend): add 1px scan-line refetch indicator to Layout"
- Modify: `src/lib/api.ts` - Modify: `src/lib/api.ts`
- Create: `src/lib/api.test.ts` (extend with 3 tests) - Create: `src/lib/api.test.ts` (extend with 3 tests)
- [ ] **Step 1: Add GET methods + types** - [x] **Step 1: Add GET methods + types**
In `src/lib/api.ts`, before the `export const api = {` line, add: In `src/lib/api.ts`, before the `export const api = {` line, add:
```ts ```ts
@@ -2174,7 +2174,7 @@ export const api = {
}; };
``` ```
- [ ] **Step 2: Add 3 unit tests in `src/lib/api.test.ts`** - [x] **Step 2: Add 3 unit tests in `src/lib/api.test.ts`**
Create `src/lib/api.test.ts`: Create `src/lib/api.test.ts`:
```ts ```ts
@@ -2233,12 +2233,12 @@ describe("api GET helpers", () => {
> If `vitest` is not already configured, add `"test": "vitest run"` to `package.json` `scripts` and `vitest` to devDependencies. Run `npm install -D vitest` if needed. > If `vitest` is not already configured, add `"test": "vitest run"` to `package.json` `scripts` and `vitest` to devDependencies. Run `npm install -D vitest` if needed.
- [ ] **Step 3: Run the tests** - [x] **Step 3: Run the tests**
Run: `npm test -- src/lib/api.test.ts` Run: `npm test -- src/lib/api.test.ts`
Expected: 3 tests pass. Expected: 3 tests pass.
- [ ] **Step 4: Commit** - [x] **Step 4: Commit**
```bash ```bash
git add src/lib/api.ts src/lib/api.test.ts package.json package-lock.json git add src/lib/api.ts src/lib/api.test.ts package.json package-lock.json
@@ -2257,7 +2257,7 @@ git commit -m "feat(frontend): add listBatches/getBatch/listClaims/listRemittanc
- Create: `src/hooks/useActivity.ts` - Create: `src/hooks/useActivity.ts`
- Create: `src/hooks/useParse.ts` - Create: `src/hooks/useParse.ts`
- [ ] **Step 1: `useBatches.ts`** - [x] **Step 1: `useBatches.ts`**
```ts ```ts
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
@@ -2272,7 +2272,7 @@ export function useBatches() {
} }
``` ```
- [ ] **Step 2: `useClaims.ts`** (with fallback to zustand when `!api.isConfigured`) - [x] **Step 2: `useClaims.ts`** (with fallback to zustand when `!api.isConfigured`)
```ts ```ts
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
@@ -2327,7 +2327,7 @@ export function useClaims(params: ListClaimsParams) {
export { EMPTY as EMPTY_CLAIMS }; export { EMPTY as EMPTY_CLAIMS };
``` ```
- [ ] **Step 3: `useRemittances.ts`** - [x] **Step 3: `useRemittances.ts`**
```ts ```ts
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
@@ -2367,7 +2367,7 @@ export function useRemittances(params: ListRemittancesParams) {
} }
``` ```
- [ ] **Step 4: `useProviders.ts`** - [x] **Step 4: `useProviders.ts`**
```ts ```ts
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
@@ -2400,7 +2400,7 @@ export function useProviders(params: ListProvidersParams = {}) {
} }
``` ```
- [ ] **Step 5: `useActivity.ts`** - [x] **Step 5: `useActivity.ts`**
```ts ```ts
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
@@ -2434,7 +2434,7 @@ export function useActivity(params: ListActivityParams = {}) {
} }
``` ```
- [ ] **Step 6: `useParse.ts`** - [x] **Step 6: `useParse.ts`**
```ts ```ts
import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQueryClient } from "@tanstack/react-query";
@@ -2458,7 +2458,7 @@ export function useParse(kind: "837p" | "835") {
} }
``` ```
- [ ] **Step 7: Verify build + commit** - [x] **Step 7: Verify build + commit**
```bash ```bash
npm run build npm run build
@@ -2473,7 +2473,7 @@ git commit -m "feat(frontend): add 6 query/mutation hooks (batches, claims, remi
**Files:** **Files:**
- Modify: `src/pages/Claims.tsx` - Modify: `src/pages/Claims.tsx`
- [ ] **Step 1: Replace the file** - [x] **Step 1: Replace the file**
Replace `src/pages/Claims.tsx` with: Replace `src/pages/Claims.tsx` with:
```tsx ```tsx
@@ -2779,7 +2779,7 @@ export function Claims() {
} }
``` ```
- [ ] **Step 2: Verify build + commit** - [x] **Step 2: Verify build + commit**
```bash ```bash
npm run build npm run build
@@ -2794,7 +2794,7 @@ git commit -m "feat(frontend): refactor Claims page to useClaims + skeleton/empt
**Files:** **Files:**
- Modify: `src/pages/Remittances.tsx` - Modify: `src/pages/Remittances.tsx`
- [ ] **Step 1: Replace the file** - [x] **Step 1: Replace the file**
Replace `src/pages/Remittances.tsx` with: Replace `src/pages/Remittances.tsx` with:
```tsx ```tsx
@@ -2945,7 +2945,7 @@ export function Remittances() {
} }
``` ```
- [ ] **Step 2: Verify build + commit** - [x] **Step 2: Verify build + commit**
```bash ```bash
npm run build npm run build
@@ -2960,7 +2960,7 @@ git commit -m "feat(frontend): refactor Remittances page to useRemittances + UX
**Files:** **Files:**
- Modify: `src/pages/Providers.tsx` - Modify: `src/pages/Providers.tsx`
- [ ] **Step 1: Replace the file** - [x] **Step 1: Replace the file**
Replace `src/pages/Providers.tsx` with: Replace `src/pages/Providers.tsx` with:
```tsx ```tsx
@@ -3068,7 +3068,7 @@ export function Providers() {
} }
``` ```
- [ ] **Step 2: Verify build + commit** - [x] **Step 2: Verify build + commit**
```bash ```bash
npm run build npm run build
@@ -3083,7 +3083,7 @@ git commit -m "feat(frontend): refactor Providers page to useProviders + UX patt
**Files:** **Files:**
- Modify: `src/pages/ActivityLog.tsx` - Modify: `src/pages/ActivityLog.tsx`
- [ ] **Step 1: Replace the file** - [x] **Step 1: Replace the file**
Replace `src/pages/ActivityLog.tsx` with: Replace `src/pages/ActivityLog.tsx` with:
```tsx ```tsx
@@ -3136,7 +3136,7 @@ export function ActivityLog() {
} }
``` ```
- [ ] **Step 2: Verify build + commit** - [x] **Step 2: Verify build + commit**
```bash ```bash
npm run build npm run build
@@ -3151,7 +3151,7 @@ git commit -m "feat(frontend): refactor ActivityLog page to useActivity + UX pat
**Files:** **Files:**
- Modify: `src/pages/Upload.tsx` - Modify: `src/pages/Upload.tsx`
- [ ] **Step 1: Replace the file** - [x] **Step 1: Replace the file**
Replace `src/pages/Upload.tsx` with: Replace `src/pages/Upload.tsx` with:
```tsx ```tsx
@@ -3592,7 +3592,7 @@ export function Upload() {
} }
``` ```
- [ ] **Step 2: Verify build + commit** - [x] **Step 2: Verify build + commit**
```bash ```bash
npm run build npm run build
@@ -3604,17 +3604,17 @@ git commit -m "feat(frontend): refactor Upload page to use useParse mutation"
### Task 27: Run frontend smoke (build + dev server check) ### Task 27: Run frontend smoke (build + dev server check)
- [ ] **Step 1: Build** - [x] **Step 1: Build**
Run: `npm run build` Run: `npm run build`
Expected: passes (no TS errors, no unused-import warnings). Expected: passes (no TS errors, no unused-import warnings).
- [ ] **Step 2: Type-check** - [x] **Step 2: Type-check**
Run: `npm run typecheck` Run: `npm run typecheck`
Expected: passes. Expected: passes.
- [ ] **Step 3: Commit any fixups (likely none)** - [x] **Step 3: Commit any fixups (likely none)**
```bash ```bash
git status # should be clean git status # should be clean
@@ -3629,7 +3629,7 @@ git status # should be clean
**Files:** **Files:**
- Create: `docs/reference/837p.md` - Create: `docs/reference/837p.md`
- [ ] **Step 1: Create the file** - [x] **Step 1: Create the file**
```markdown ```markdown
# 837P — Professional Claims (005010X222A1) # 837P — Professional Claims (005010X222A1)
@@ -3699,7 +3699,7 @@ full walker.
| `R300_subscriber_present` | error | `NM1*IL` (subscriber) present | | `R300_subscriber_present` | error | `NM1*IL` (subscriber) present |
``` ```
- [ ] **Step 2: Commit** - [x] **Step 2: Commit**
```bash ```bash
git add docs/reference/837p.md git add docs/reference/837p.md
@@ -3713,7 +3713,7 @@ git commit -m "docs: add 837p reference note"
**Files:** **Files:**
- Create: `docs/reference/835.md` - Create: `docs/reference/835.md`
- [ ] **Step 1: Create the file** - [x] **Step 1: Create the file**
```markdown ```markdown
# 835 — Electronic Remittance Advice (005010X221A1) # 835 — Electronic Remittance Advice (005010X221A1)
@@ -3791,7 +3791,7 @@ date of service is deferred to sub-project 2. Cyclone currently stores the
835 output as-is. 835 output as-is.
``` ```
- [ ] **Step 2: Commit** - [x] **Step 2: Commit**
```bash ```bash
git add docs/reference/835.md git add docs/reference/835.md
@@ -3805,7 +3805,7 @@ git commit -m "docs: add 835 reference note"
**Files:** **Files:**
- Create: `docs/reference/x12naming.md` - Create: `docs/reference/x12naming.md`
- [ ] **Step 1: Create the file** - [x] **Step 1: Create the file**
```markdown ```markdown
# X12 naming conventions # X12 naming conventions
@@ -3876,7 +3876,7 @@ rest of the file by those characters.
- `^` — repetition separator - `^` — repetition separator
``` ```
- [ ] **Step 2: Commit** - [x] **Step 2: Commit**
```bash ```bash
git add docs/reference/x12naming.md git add docs/reference/x12naming.md
@@ -3890,7 +3890,7 @@ git commit -m "docs: add x12 naming reference note"
**Files:** **Files:**
- Create: `docs/reference/co-medicaid.md` - Create: `docs/reference/co-medicaid.md`
- [ ] **Step 1: Create the file** - [x] **Step 1: Create the file**
```markdown ```markdown
# Colorado Medicaid — payer specifics # Colorado Medicaid — payer specifics
@@ -3966,7 +3966,7 @@ The pattern is:
4. Update this note + the 837p/835 notes with the new payer's specifics. 4. Update this note + the 837p/835 notes with the new payer's specifics.
``` ```
- [ ] **Step 2: Commit** - [x] **Step 2: Commit**
```bash ```bash
git add docs/reference/co-medicaid.md git add docs/reference/co-medicaid.md
@@ -3980,7 +3980,7 @@ git commit -m "docs: add CO Medicaid reference note"
**Files:** **Files:**
- Modify: `README.md` - Modify: `README.md`
- [ ] **Step 1: Replace the file** - [x] **Step 1: Replace the file**
Replace `README.md` (root) with: Replace `README.md` (root) with:
```markdown ```markdown
@@ -4103,7 +4103,7 @@ No license file yet; this is internal-use software. Add a `LICENSE` file
when one is decided. when one is decided.
``` ```
- [ ] **Step 2: Commit** - [x] **Step 2: Commit**
```bash ```bash
git add README.md git add README.md
@@ -4116,22 +4116,22 @@ git commit -m "docs: rewrite root README for sub-project 1"
### Task 33: End-to-end smoke test (manual + documented) ### Task 33: End-to-end smoke test (manual + documented)
- [ ] **Step 1: Start the backend** - [x] **Step 1: Start the backend**
Terminal 1: `cd backend && .venv/bin/python -m cyclone serve` Terminal 1: `cd backend && .venv/bin/python -m cyclone serve`
Expected: log line "Uvicorn running on http://127.0.0.1:8000". Expected: log line "Uvicorn running on http://127.0.0.1:8000".
- [ ] **Step 2: Confirm loopback-only** - [x] **Step 2: Confirm loopback-only**
Terminal 2: `curl -s http://127.0.0.1:8000/api/health | jq .``{"status": "ok", "version": "0.1.0"}`. Terminal 2: `curl -s http://127.0.0.1:8000/api/health | jq .``{"status": "ok", "version": "0.1.0"}`.
Then: `curl -s http://0.0.0.0:8000/api/health` → connection refused. Then: `curl -s http://0.0.0.0:8000/api/health` → connection refused.
- [ ] **Step 3: Start the frontend** - [x] **Step 3: Start the frontend**
Terminal 3: `npm run dev` Terminal 3: `npm run dev`
Expected: "Local: http://localhost:5173". Expected: "Local: http://localhost:5173".
- [ ] **Step 4: Drop a fixture file and verify live data flow** - [x] **Step 4: Drop a fixture file and verify live data flow**
1. Open `http://localhost:5173/upload` in a browser. 1. Open `http://localhost:5173/upload` in a browser.
2. Drop `backend/tests/fixtures/co_medicaid_837p.txt` on the upload zone. 2. Drop `backend/tests/fixtures/co_medicaid_837p.txt` on the upload zone.
@@ -4143,18 +4143,18 @@ Expected: "Local: http://localhost:5173".
8. Apply `Status: submitted` filter on `/claims`. Expect: the same 2 rows; the `FilterChips` row shows "Status: submitted ×". 8. Apply `Status: submitted` filter on `/claims`. Expect: the same 2 rows; the `FilterChips` row shows "Status: submitted ×".
9. Watch the top of `<main>` while navigating back to `/upload` and re-parsing — the 1px scan-line indicator should appear briefly. 9. Watch the top of `<main>` while navigating back to `/upload` and re-parsing — the 1px scan-line indicator should appear briefly.
- [ ] **Step 5: Verify NDJSON streaming** - [x] **Step 5: Verify NDJSON streaming**
Terminal 2: `curl -s -N -H 'Accept: application/x-ndjson' http://127.0.0.1:8000/api/claims | head -5` Terminal 2: `curl -s -N -H 'Accept: application/x-ndjson' http://127.0.0.1:8000/api/claims | head -5`
Expected: 2 lines of `{"type":"item",…}` + 1 line of `{"type":"summary",…}`. Expected: 2 lines of `{"type":"item",…}` + 1 line of `{"type":"summary",…}`.
- [ ] **Step 6: Stop the backend and verify the UI reverts to the fallback** - [x] **Step 6: Stop the backend and verify the UI reverts to the fallback**
Kill terminal 1 (`Ctrl+C`). Refresh the browser. Expect: the pages still Kill terminal 1 (`Ctrl+C`). Refresh the browser. Expect: the pages still
render (using the zustand fallback via the existing `data` adapter), but render (using the zustand fallback via the existing `data` adapter), but
the upload page shows "No backend configured". the upload page shows "No backend configured".
- [ ] **Step 7: Final commit (likely none)** - [x] **Step 7: Final commit (likely none)**
```bash ```bash
git status # should be clean git status # should be clean