plans/ held 58 documents and exactly one said whether it was open. The rest
mixed finished work, reviews of shipped work, parked specs and genuinely
pending ones, with nothing distinguishing them, so "how many plans are in
the queue" had no answer short of reading all 58.
Now `grep -H '^Status:' plans/*.md` is the answer:
50 done 3 in progress 2 planned 2 reference 1 parked
Statuses were derived rather than guessed: CLAUDE.md's own built list and
"What's NOT built yet" section, plus checking the subject exists in the
code. A review of work that shipped counts as done -- it records what was
found, it is not a request for anything. `reference` separates the two docs
that are conventions rather than work items (admin-design-standards,
admin-work-framework), which otherwise read as permanently-open plans.
The vocabulary is deliberately five words. A larger one invites "mostly
done" and "blocked-ish", which is how the directory became unreadable.
test_plans_declare_status.py keeps it from rotting: a new plan without a
marker fails, as does an unknown status, one buried below the eighth line,
or an open status with no reason -- "planned" alone is the state that rots,
since nobody can tell later whether it waits on a decision, a dependency,
or just nobody's turn.
Also updates the sweep plan with what landed and what did not, including
that #9 was not a defect.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VRQXz5SYZYVWscxS1QqF6U
6.0 KiB
TUI overhaul: catch the dashboard up to the schema
Status: done -- tui.py plus the tui_model.py split
Status: FINAL — decision-complete. Written 2026-09-04 against main at
a0c7e8e.
Why now
The TUI (src/tui.py, tui_model.py, tui_screens.py, tui_sse.py — ~1,000
lines total) has drifted behind the data it displays. Five columns were added
to route_decisions across this session's merges and none reached the
dashboard, and the decision table has never had a timestamp.
The admin portal got a Profile column in PR #25. The TUI did not. It is the same data.
1. The decision table is missing a timestamp
Declared columns (src/tui.py:314-316):
"id", "kind", "category", "tier", "ctx", "selected", "est $", "flex"
observed_at is already carried by decision_row in tui_model.py — it
is fetched and then never rendered. So this is a display change, not a data
change.
Add a time column. Render it short (HH:MM:SS), not the full ISO
timestamp: the rows are dense, the date is almost always today, and the full
form would crowd out selected, which is the column people actually read.
Put it first — it is the natural scan axis for a live feed.
2. Five columns exist in the schema and are invisible
Measured by diffing PRAGMA table_info(route_decisions) against what
decision_row exposes:
| column | shipped in | why it matters |
|---|---|---|
profile |
PR #25 | which named profile served the request |
exploration |
proficiency branch | whether epsilon-greedy picked this, not the ranking |
pinch_original_tokens |
PR #18 | context pruning input |
pinch_final_tokens |
PR #18 | context pruning output |
request_id |
proficiency branch | the join key to /outcome reports |
session_key |
earlier | hashed session fingerprint |
Do not add six more columns to the table. It already has eight and the terminal is not wide. Instead:
- Add
profileto the table proper. It changes which models were even considered, so a decision cannot be read without it — the same argument that earned it a column in the admin portal. - Add an
Eflag in the existing flags idiom forexploration, alongside howflexis already rendered. An exploratory pick is not a ranking result and must be visually distinguishable, or the operator reads a deliberate random sample as the router's judgement. - Put
pinch_*,request_id,session_keyin the detail popup (tui_screens.py, opened with Enter ore), which already shows the full decision JSON. They are per-decision forensics, not scan-axis data.
3. The quota panel shows the wrong thing
This section depends on plans/quota-balance-and-burn-rate.md and must not
land before it. That plan replaces percentage-of-plan with balance and burn
rate, because the current framing is measurably wrong: the warning says "a
quota is a wall, not a bill — requests fail rather than costing more" while
usage sat at 146% of plan and nothing failed, since the provider bills overage
against a credit balance.
Once that lands, the TUI panel (#quota-panel, #quota-progress,
#quota-legend) should lead with balance and projected runway
("$12.19 left, ~23h at current burn") and demote the percentage bar to
secondary. A progress bar against a plan figure that is routinely exceeded is
actively misleading — it implies a ceiling that does not exist.
Sequencing: if the quota plan has not landed when this one runs, do items
1, 2 and 4 and leave the panel alone. Do NOT reimplement balance/burn
independently in the TUI — metrics.quota_burn is the single source and the
TUI reads /metrics.
4. Surface the warnings that already exist
coverage.warnings from /metrics already carries catalog staleness, quota
burn, scoring coverage gaps and ceiling warnings. #warnings-panel exists.
Confirm every warning class actually reaches it — the vision-ceiling incident
on 2026-09-04 showed a whole warning family that was computed and never
displayed, and the fix there was surfacing, not computing.
This is a verification task as much as a feature: for each warning the
/metrics coverage.warnings list can emit, assert it renders.
Non-goals
- No new data. Everything here is already in
route_decisionsor/metrics. - Do not widen the decision table beyond one added column plus one flag.
- Do not reimplement any metric in the TUI.
tui_model.pyis the pure data layer over/metricsand/events/decisions; keep the computation inmetrics.py. - Do not import
textualoutside the TUI modules. The dispatch path must stay free of the UI dependency — that separation is deliberate and tested. - No colour/theme rework. This is about information, not appearance.
Success criteria
- The decision table shows a short
HH:MM:SStime column, first. profileis a column;explorationrenders as a flag besideflex.pinch_original_tokens,pinch_final_tokens,request_idandsession_keyappear in the detail popup.- A test diffs
PRAGMA table_info(route_decisions)against what the TUI model exposes and fails if a column is added to the schema without a decision about surfacing it. This is the test that stops the drift recurring — the rest of this plan is a one-time catch-up, this is the part that keeps it caught up. - Every warning class
/metricscan emit renders in#warnings-panel. - Quota panel leads with balance and runway if the quota plan has landed; otherwise untouched and noted.
textualstill imported only by TUI modules (existing test stays green).- Full suite green with
local_energy.enabledboth true and false. - The user's
config/config.local.yamlis byte-identical after the run (local_energy.enabled: true,tariff_usd_per_kwh: 0.159). Corrected 2026-09-05: this previously namedconfig/config.yaml. PR #26 moved deployment values into the gitignored overlay, soconfig/config.yamlis clean and tracked, and editing it is an ordinary commit. The file that cannot be recovered is the overlay — it is not in git history at all.