Files
box-box/documentations/refactor/12-phase-4-local-first-web-api.md

140 lines
4.0 KiB
Markdown
Raw Normal View History

2026-05-25 00:45:46 -04:00
# Phase 4 Local-First Web API
## Purpose
Phase 4 makes the Web API start behaving like a local-first product. Phases 2
and 3 created the domain store and explicit ingestion path; this phase adds
read models that prefer local SQLite data and report data availability honestly.
This is still a backend phase. Do not start React yet.
## Manager Decision
Build one credible local-first Race Hub API slice before replacing the frontend.
The current Web UI can keep working from the existing endpoints, but the backend
should expose store-backed responses that a future React Race Hub can trust.
Phase 4 should add:
- store-backed read models for ingested meetings, sessions, drivers, results,
and grid;
- dataset/status metadata so the UI knows what is local, missing, or stale;
- optional small API fallbacks only when explicitly requested;
- tests for local-first behavior without network.
## Package Boundary
Prefer adding a backend read-model layer instead of embedding SQL inside HTTP
handlers.
Recommended shape:
```text
internal/query/
racehub.go Race Hub read model assembly
metadata.go dataset availability/source metadata
query_test.go temp-db tests
```
Then wire `internal/web` to use that layer.
If the implementation keeps the read layer inside `internal/web` temporarily,
it must still avoid duplicating store SQL across handlers.
## Initial API Scope
Add a new Race Hub endpoint:
```text
GET /api/v1/race-hub?session_key=9472
```
Response should include:
- meeting;
- session;
- drivers;
- session results enriched with driver/team fields;
- starting grid enriched with driver/team fields;
- dataset availability metadata.
Recommended metadata shape:
```json
{
"source": "local",
"session_key": 9472,
"datasets": {
"meeting": {"status": "available", "source": "local"},
"session": {"status": "available", "source": "local"},
"drivers": {"status": "available", "source": "local", "count": 20},
"results": {"status": "missing", "source": "none", "count": 0},
"starting_grid": {"status": "available", "source": "local", "count": 20}
}
}
```
Exact field names can vary, but the response must make missing datasets visible
instead of silently returning empty app states.
## Existing Endpoint Policy
Do not rewrite every existing endpoint yet. It is enough to:
- add the new local-first Race Hub endpoint;
- optionally make `/api/v1/meetings`, `/api/v1/sessions`, `/api/v1/drivers`,
`/api/v1/results`, and `/api/v1/grid` read from local data when present;
- preserve old OpenF1 behavior when local data is absent unless the request asks
for local-only behavior.
Recommended query controls:
```text
?source=local local only; no OpenF1 fallback
?source=auto local first, existing OpenF1 fallback when missing
```
Default should be conservative for existing endpoints. The new Race Hub endpoint
can default to local-first with honest missing metadata.
## Server Wiring
`web.Server` currently only receives `*api.OpenF1Client`. Add an optional
`*store.Store` or query service so Web mode can read the domain DB.
CLI/server behavior should remain simple:
```bash
go run cmd/main.go --web
go run cmd/main.go --web --db /path/to/boxbox.db
```
If the DB does not exist or has no ingested data, Web mode should still start.
## Non-Goals
Do not include these in Phase 4:
- React/Vite frontend setup.
- replacing the current static Web UI;
- automatic ingestion from Web browsing;
- live SignalR persistence;
- laps/stints/pits/weather/race-control read models unless the store schema is
expanded and tested first.
## Acceptance Criteria
Phase 4 is complete when:
- a local-first Race Hub endpoint exists;
- it can return ingested session data without OpenF1 calls;
- it reports missing datasets explicitly;
- Web mode can be pointed at a domain DB with `--db`;
- offline tests cover the read model and HTTP handler behavior;
- focused tests and build pass.
## Next Phase After This
Phase 5 is the first frontend implementation phase. That is the point to switch
from Cursor to Claude for React/UI work.