Files
6krrt/plans/admin-visual-fixes-review.md
adlee-was-taken 3523dcf93e docs(plans): give every plan a Status line so the queue is greppable
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
2026-09-08 18:55:16 -04:00

5.9 KiB

Review: admin-visual-fixes implementation

Status: done -- review of shipped work

What it was reviewing: the 8-todo admin-visual-fixes plan (plans/.omo/plans/admin-visual-fixes.md), after its orchestration reported 8/8 complete and F1-F4 all APPROVE. Verified against the actual diff and the plan's own saved QA evidence (plans/.omo/evidence/task-8-admin-visual-fixes-console.txt), not the orchestration summary.

Verdict: 6 of 8 todos are genuinely fixed; Todo 2 is not, and the plan's own evidence file proves it

Confirmed correct

  • Todo 1 (quota bar): gauge-canvas fully removed, .quota-bar markup in place.
  • Todo 3 (history chart): canvas id fixed to history-chart and historyChart = renderChart(...) correctly captures the instance (admin/frontend/index.html:610).
  • Todo 4: knobDisplayValue() (index.html:687-696) correctly unwraps {enabled: bool} before display.
  • Todo 5: renderWarnings (index.html:538-547) rebuilds the #no-warnings placeholder inline instead of referencing a detached node — no more stale reference.
  • Todo 6 (XSS): task_category and modelStr (selected_model/ selected_provider) are now escapeHtml()-wrapped in both renderDecisions and addDecision. rejected_reason/runner_up_models are never rendered anywhere in the file, so there was nothing to escape there.
  • Todo 7 (config race): correct and thorough. _persist_config_value (admin.py:349-374) takes a module-level threading.Lock around the whole load→validate→backup→write→replace sequence (the right primitive — admin_config_write is a sync handler, which FastAPI runs via anyio.to_thread, so asyncio.Lock would not have serialized anything), validates before backing up, and writes via .tmp + os.replace so a reader never observes a truncated file. tests/test_admin_config.py::test_config_concurrent_writes_are_atomic_no_zero_byte_backups is a real regression test — a live reader thread asserts the file is never empty while two writer threads race. saveAllConfig() (index.html:774-804) is now a sequential for...of await loop, not Promise.all. All 57 admin tests pass.

Not fixed: Todo 2 (verdict doughnut) — and the evidence already proves it

renderVerdict (index.html:508-536) fixed the canvas id (verdict-canvas → verdict-chart) but never assigns the created chart back to the verdictChart variable:

if (verdictChart) { updateChart('verdict', labels, values, 'doughnut', colors); }
else { renderChart('verdict-chart', labels, values, 'doughnut', colors); }   // return value dropped

verdictChart stays null forever, so every 30-second poll (REFRESH_MS) takes the else branch again and tries to create a second Chart.js instance on a canvas that already has one attached. This is already recorded in the plan's own saved evidence, task-8-admin-visual-fixes-console.txt:

Uncaught (in promise) Error: Canvas is already in use. Chart with ID '1' must be
destroyed before the canvas with ID 'verdict-chart' can be reused.
    at renderChart (http://127.0.0.1:8091/admin/:831:11)
    at renderVerdict (http://127.0.0.1:8091/admin/:531:9)
    at loadSnapshot (http://127.0.0.1:8091/admin/:332:4)

— repeated 6 times in the log. The doughnut renders once on first load, then throws and stops updating for the rest of the page's life. This directly contradicts F3's "clean console" sign-off and Todo 2's own acceptance criteria ("the verdict doughnut renders"); a single screenshot taken right after page load wouldn't catch it, but the saved console log already had it and nobody read it before approving.

Fix: mirror what historyChart already does correctly two functions over — capture the return value:

else { verdictChart = renderChart('verdict-chart', labels, values, 'doughnut', colors); }

Also update the dead-code branch at index.html:512 (renderChart('verdict-chart', null, 'verdict') — missing arguments, never actually reassigns anything either) to just verdictChart = null; without the pointless re-render call, since there's no data to draw.

Secondary, out-of-scope-but-worth-a-look: objective.max_energy_per_request 422s on every "Save All"

Same evidence file shows two 422 responses for POST /admin/api/config/objective.max_energy_per_request during the save-all QA pass. config.yaml has this key as null; the config table's text input renders it as an empty string, and saveAllConfig's number-coercion (Number('') → 0, but the val.trim() !== '' guard correctly keeps it as '' rather than coercing to 0) ends up POSTing an empty string against an Optional[float] field, which RouterConfig rejects. Not data-destructive (validation fails before the backup/write step, exactly as designed) and not part of any of the 8 todos' scope, but worth a follow-up: null-valued numeric config fields can't currently be round-tripped through "Save All" without a spurious failure toast.

Process gap, not a code defect

No commits landed for any of the 8 todos despite the plan's explicit "one commit per todo (8 total)" commit strategy — git log still ends at b63234a, and admin.py / admin/frontend/index.html remain untracked. Separately, the live llm-router.service has not restarted since 02:35:45 (same PID throughout), so the Todo 7 fix — the one that matters for production — is sitting on disk but not yet protecting the running service.

Recommendation

Fix the one-line verdictChart assignment (plus the dead branch at :512), re-run the same Playwright QA against a fresh page load and at least one 30s+ idle period to confirm the console actually stays clean across a poll cycle this time — a single screenshot won't catch this class of bug again. Then commit per the plan's own commit strategy. Restarting llm-router.service to pick up the Todo 7 fix is a separate, explicit step to take afterward, not bundled into the same action.