fix spec: correct file paths for feed assembly, route files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 12:44:43 +01:00
parent 7702b49fc0
commit 821d7cc4ca

View File

@@ -10,7 +10,7 @@
### 1a. Retry Utility
**File:** `src/shared/retry.ts`
**File:** `src/shared/retry.ts` (isomorphic — used by both server API clients and client feed assembly)
Generic `withRetry<T>(fn, opts)`:
- Exponential backoff, configurable max attempts (default 3), base delay (default 500ms)
@@ -18,6 +18,8 @@ Generic `withRetry<T>(fn, opts)`:
- Only retries on transient failures: network errors, 5xx, 429
- Does not retry 4xx or Zod validation errors
Placed in `src/shared/` because both server (2a, 2c) and client (3a feed fetches) consume it.
**Tests:** Unit tests for retry logic, backoff timing, abort behavior, non-retryable error passthrough.
### 1b. Error Boundary Component
@@ -48,11 +50,14 @@ Generic `withRetry<T>(fn, opts)`:
**Files:** `src/server/shared/jobs/poll-checker.ts`, `src/server/shared/jobs/legislation-syncer.ts`
- Replace `Promise.all` with `Promise.allSettled` for batch vote/vorgänge fetching
- Log failures, continue with successful results (partial success)
- Structured log format: `{ job, action, id, error }` for parseable journalctl output
Both jobs use sequential `for` loops with individual `await` calls. The legislation-syncer already has per-item try-catch; the poll-checker is missing try-catch around several inner awaits (e.g., `resolveMandateId`, vote fetching inside the per-device notification loop), which can break the entire iteration on a single failure.
**Tests:** Mock partial API failures, verify job completes with available data and logs errors.
Fixes:
- Poll-checker: wrap every `await` inside the per-poll and per-device loops in try-catch — log the error with structured format and continue to the next item
- Legislation-syncer: verify existing try-catch coverage is complete, add structured logging
- Structured log format for both: `{ job, action, id, error }` for parseable journalctl output
**Tests:** Mock individual API call failures mid-iteration, verify job processes remaining items and logs errors.
### 2c. Push Notification Error Handling
@@ -87,7 +92,7 @@ Fixes:
**Files:** `src/client/features/feed/lib/assemble-feed.ts`, `src/client/features/feed/hooks/use-feed.ts`
- Replace `Promise.all` with `Promise.allSettled` for poll/vote fetching
- Replace `Promise.all` with `Promise.allSettled` for poll/vote fetching in `assemble-feed.ts` (the single assembly file that handles all feed types)
- Surface partial failures as a non-blocking warning banner ("some data couldn't be loaded")
- Await `saveFeedCache()` + wrap in try-catch (currently fire-and-forget)
- Catch `loadFeedCache()` DB errors, fall back to empty cache
@@ -101,11 +106,13 @@ Fixes:
- Debounce follows sync: 300ms debounce when follows change rapidly
- In-flight guard: if sync already running, queue the next one instead of concurrent requests
**Tests:** Simulate rapid follow/unfollow, verify only one sync request in-flight, final state correct.
**Tests:** Use `vi.useFakeTimers()` to simulate rapid follow/unfollow within the debounce window. Mock `syncFollowsToBackend` to track calls. Verify only one sync request in-flight, final state includes all changes.
### 3c. IndexedDB Error Handling
**Files:** All modules in `src/client/shared/db/``follows.ts`, `feed-cache-db.ts`, `geo-cache-db.ts`, `push-state-db.ts`, `device.ts`
**Files:** `src/client/shared/db/follows.ts`, `feed-cache-db.ts`, `geo-cache-db.ts`, `push-state-db.ts`, `device.ts`
Excluded: `client.ts` (PGlite singleton — init errors are already fatal by design) and `migrate-from-localstorage.ts` (one-time migration with its own error handling).
- Wrap all PGlite query calls with try-catch
- On failure: log error, return sensible defaults (empty arrays, null)
@@ -139,11 +146,11 @@ Fixes:
### 4a. Route Error Boundaries
Wrap each major route with error boundary from 1b:
- `bundestag`, `landtag` — "couldn't load votes"
- `legislation.$legislationId` — "couldn't load legislation"
- `politician.$politicianId` — "couldn't load politician profile"
- `representatives` — "couldn't load representatives"
- Root layout keeps last-resort catch-all
- `src/client/routes/app/bundestag/index.tsx`, `src/client/routes/app/landtag/index.tsx` — "couldn't load votes"
- `src/client/routes/app/legislation.$legislationId.tsx` — "couldn't load legislation"
- `src/client/routes/app/politician.$politicianId.tsx` — "couldn't load politician profile"
- `src/client/routes/app/representatives.tsx` — "couldn't load representatives"
- `src/client/routes/__root.tsx` keeps last-resort catch-all
### 4b. Loading/Empty States
@@ -158,7 +165,7 @@ No additional tests beyond 1b's error boundary tests. This section is primarily
### 5a. Server Integration Tests
- **Legislation router:** Full request→response for GET /upcoming, GET /:id, POST /:id/vote, GET /:id/results/:deviceId, including 400/404 paths
- **Legislation router:** Full request→response for GET /upcoming, GET /:id, POST /:id/vote, GET /:id/results/:deviceId, GET /dip-proxy/vorgaenge, GET /dip-proxy/vorgaenge/:id, including 400/404 paths
- **Politician router:** Cache hit vs miss, invalid ID, AW API failure → 500
- **Push router:** Subscribe → sync → test → unsubscribe lifecycle, invalid payloads