Adds a real-time view of actual routing tasks to the TUI dashboard:
backend:
- events.py: in-memory decision-event broker (pure stdlib, thread-safe).
Bounded ring buffer + fan-out queues. persist_route_decision publishes
here after each write so the TUI sees decisions without polling.
- dispatcher.py: GET /events/decisions SSE endpoint — replays recent
decisions then streams live ones with :heartbeat keepalive. Wired into
persist_route_decision's write path.
tui:
- tui.py: DashboardApp now consumes /events/decisions via a background
thread (call_from_thread). Decisions table updates live without waiting
for the 5s /metrics poll. New columns: id, kind, category, tier, ctx,
selected, est $. Number keys 1-6 cycle panels.
- tui_screens.py: DecisionDetailScreen modal — press Enter or e on any
decision row to see the full JSON (runner-ups, rejected reason, feature
flags, confidence, context size).
- tui_model.py: pure data layer extracted from tui.py — build_model,
build_category_breakdown, decision_row. Testable without a terminal.
- tui_sse.py: background-thread SSE consumer with reconnect.
17 new tests (events broker, SSE endpoint, TUI data model, detail popup,
live decision handling). 562 total, all passing. lsp_diagnostics clean.