mirror of
https://github.com/AmanTahiliani/box-box.git
synced 2026-08-07 19:56:18 -04:00
Add local-first refactor foundation
This commit is contained in:
195
documentations/refactor/09-phase-1-live-extraction.md
Normal file
195
documentations/refactor/09-phase-1-live-extraction.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# Phase 1 Live Extraction
|
||||
|
||||
## Purpose
|
||||
|
||||
Phase 1 creates a stable live timing foundation without changing the product
|
||||
surface. The current live mode is the strongest part of `box-box`, but the core
|
||||
SignalR connection and parsing code lives inside `internal/ui`. That creates a
|
||||
bad dependency direction: the Web server imports TUI code only to access live
|
||||
data types and `ConnectToF1LiveTiming`.
|
||||
|
||||
The goal is to extract the reusable live timing core into `internal/live`, keep
|
||||
the TUI and Web UI working, and add fixture-based tests around the parsing
|
||||
surface. This is a foundation phase, not a frontend redesign phase.
|
||||
|
||||
## Manager Decision
|
||||
|
||||
I agree with Claude that Race Hub is the safest first React product slice.
|
||||
However, before React work starts, the live timing backend should be separated
|
||||
from the TUI. The current Web UI already depends on live data through SSE, and
|
||||
future React live screens will need that source without importing terminal UI
|
||||
code.
|
||||
|
||||
Therefore Phase 1 is:
|
||||
|
||||
- Extract the live SignalR bridge into `internal/live`.
|
||||
- Update TUI live mode to consume `internal/live`.
|
||||
- Update Web SSE live mode to consume `internal/live`.
|
||||
- Add tests for live message parsing/state updates.
|
||||
- Do not add persistence, React, or new UI behavior yet.
|
||||
|
||||
## Current Coupling To Remove
|
||||
|
||||
Current state:
|
||||
|
||||
- `internal/ui/official_live.go` owns SignalR protocol types, live data types,
|
||||
topic parsing, connection setup, and TUI rendering.
|
||||
- `internal/web/live.go` imports `internal/ui` for `ui.LiveStreamData` and
|
||||
`ui.ConnectToF1LiveTiming`.
|
||||
|
||||
Target state:
|
||||
|
||||
- `internal/live` owns reusable live data structures, SignalR protocol parsing,
|
||||
connection setup, and state update logic.
|
||||
- `internal/ui` owns Bubble Tea model state, keyboard behavior, and terminal
|
||||
rendering.
|
||||
- `internal/web` owns SSE clients, HTTP handlers, reconnect/backoff policy, and
|
||||
JSON responses.
|
||||
|
||||
## Proposed Package Boundary
|
||||
|
||||
Add:
|
||||
|
||||
```text
|
||||
internal/live/
|
||||
types.go LiveStreamData, LiveDriverData, weather, race control, tyres
|
||||
signalr.go negotiate/connect/subscribe to official F1 SignalR
|
||||
parser.go raw message parsing and topic dispatch
|
||||
state.go mutable live state accumulator and snapshot copying
|
||||
parser_test.go fixture-driven tests
|
||||
testdata/ small captured/synthetic SignalR messages
|
||||
```
|
||||
|
||||
The exact file split can change during implementation, but the boundary should
|
||||
stay clear: `internal/live` must not import `internal/ui` or Bubble Tea.
|
||||
|
||||
## API Shape
|
||||
|
||||
Keep a small API compatible with current callers:
|
||||
|
||||
```go
|
||||
package live
|
||||
|
||||
type StreamData = LiveStreamData // or a normal exported type if clearer
|
||||
|
||||
func ConnectToF1LiveTiming(dataChan chan LiveStreamData) error
|
||||
```
|
||||
|
||||
Optional improvements are allowed only if they stay small and do not force broad
|
||||
behavior changes:
|
||||
|
||||
```go
|
||||
type Client struct {
|
||||
// future room for custom http client, logger, topic list, clock, etc.
|
||||
}
|
||||
|
||||
func (c *Client) Connect(dataChan chan LiveStreamData) error
|
||||
```
|
||||
|
||||
If a `Client` is introduced, preserve the top-level
|
||||
`ConnectToF1LiveTiming(dataChan)` as a convenience wrapper so TUI and Web changes
|
||||
remain boring.
|
||||
|
||||
## What Moves From `internal/ui/official_live.go`
|
||||
|
||||
Move or duplicate-then-delete these reusable concerns into `internal/live`:
|
||||
|
||||
- `F1SignalRMessage`
|
||||
- `F1TimingLine`
|
||||
- `F1DriverListEntry`
|
||||
- `LiveTyreData`
|
||||
- `LiveRCMessage`
|
||||
- `LiveWeatherData`
|
||||
- `LiveSessionMeta`
|
||||
- `LiveSectorData`
|
||||
- `LiveDriverData`
|
||||
- `LiveStintData`
|
||||
- `LiveStreamData`
|
||||
- `ConnectToF1LiveTiming`
|
||||
- topic parsing and state accumulation helpers currently embedded in the
|
||||
connection goroutine
|
||||
- snapshot-copying logic used before sending updates
|
||||
|
||||
Keep these TUI-specific concerns in `internal/ui/official_live.go`:
|
||||
|
||||
- `OfficialLiveModel`
|
||||
- Bubble Tea messages and commands
|
||||
- viewport handling
|
||||
- keybindings
|
||||
- terminal render functions
|
||||
- battle/pit-window display logic unless it is already pure and clearly useful
|
||||
to share
|
||||
|
||||
## Tests
|
||||
|
||||
Live sessions are not always available, so Phase 1 tests must not depend on a
|
||||
current race weekend. Add fixture-based tests in `internal/live`.
|
||||
|
||||
Minimum test coverage:
|
||||
|
||||
- Parse a SignalR `R` full-state message.
|
||||
- Parse a SignalR `M` incremental update message.
|
||||
- Handle known topics without panicking:
|
||||
- `TimingData`
|
||||
- `DriverList`
|
||||
- `LapCount`
|
||||
- `ExtrapolatedClock`
|
||||
- `TrackStatus`
|
||||
- `RaceControlMessages`
|
||||
- `WeatherData`
|
||||
- `SessionInfo`
|
||||
- `CurrentTyres`
|
||||
- `TimingAppData`
|
||||
- `TimingStats`
|
||||
- Preserve existing string/float/nested-value handling in timing fields.
|
||||
- Ignore unknown topics without failing.
|
||||
- Verify snapshots copy maps/slices so downstream consumers cannot mutate
|
||||
internal accumulator state accidentally.
|
||||
|
||||
Fixtures can be small synthetic messages shaped like the official feed. They do
|
||||
not need to be full captured race payloads.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Phase 1 is complete when:
|
||||
|
||||
- `internal/web/live.go` no longer imports `internal/ui`.
|
||||
- `internal/ui/official_live.go` compiles while consuming `internal/live`.
|
||||
- The existing TUI live mode still uses the official F1 SignalR feed.
|
||||
- The existing Web live SSE path still uses the official F1 SignalR feed.
|
||||
- `go test ./...` passes.
|
||||
- Parser tests run without internet access.
|
||||
- No local database, React, or visual redesign work has been started as part of
|
||||
this phase.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
Do not include these in Phase 1:
|
||||
|
||||
- React/Vite frontend setup.
|
||||
- SQLite domain database or migrations.
|
||||
- OpenF1 ingestion refactor.
|
||||
- Live event persistence.
|
||||
- Race Hub implementation.
|
||||
- Static archive research.
|
||||
- Browser notification work.
|
||||
- Major rewrite of TUI live rendering.
|
||||
|
||||
## Risks And Guardrails
|
||||
|
||||
- The live parser currently works in practice; avoid clever rewrites that change
|
||||
behavior without tests.
|
||||
- Official F1 SignalR topic schemas can drift. Keep parsing tolerant of missing,
|
||||
empty, string, numeric, and nested values.
|
||||
- Do not make Web reconnect/backoff policy part of `internal/live` yet. The Web
|
||||
server can keep owning that operational behavior.
|
||||
- Do not make the TUI import Web code. Shared logic should flow through
|
||||
`internal/live`.
|
||||
- Preserve existing logs and user-facing behavior unless a small compile-time
|
||||
adjustment requires otherwise.
|
||||
|
||||
## Next Phase After This
|
||||
|
||||
After Phase 1, Phase 2 should start `internal/store` and the local SQLite domain
|
||||
database. Live persistence should still wait until the live data/event types have
|
||||
settled and the database provenance design is ready.
|
||||
Reference in New Issue
Block a user