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

4.0 KiB

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:

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:

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:

{
  "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:

?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:

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.