Files
6krrt/plans/admin-facelift.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

94 lines
4.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Spec: admin dashboard facelift — standardize on Tabler (dark mode)
Status: done -- admin portal shipped
**Origin.** The admin dashboard (`admin/frontend/index.html`) is a hand-rolled,
single-file dark theme — custom CSS variables, a monospace terminal
aesthetic, and vanilla-JS panels with no component library. The ask is to
replace that visual language with [Tabler](https://github.com/tabler/tabler)
— a fully open-source (MIT) Bootstrap 5 admin template with dark mode built
into the core product, not gated behind a paid tier — while keeping every
existing behavior intact. (An earlier pass at this same ask targeted
Themesberg's Volt Dashboard; switched to Tabler specifically because Volt's
dark theme may be Pro-only, a risk Tabler doesn't carry.)
## Outcome (post-lift)
Spec became reality in commits `23b74a3`–`906c9e6`. The portal is now four
pages with a consistent liquid-glass dark theme. The consolidated design
system is captured in **
## Folders/files now in the admin portal
- `admin.py` (~800 LOC) — FastAPI sub-router with API + static HTML
- `admin_schema.sql`
- `admin/frontend/index.html` — dashboard
- `admin/frontend/models.html` — model availability
- `admin/frontend/decisions.html` — decision log
- `admin/frontend/controls.html` — triggers, knobs, persisted config
## Selected architecture decisions
- **CDN for Tabler + Chart.js + Google Fonts, everything else inline.** The
pages still contain their own `<style>` and `<script>` blocks — the original
"one self-contained file" architecture from F12 was relaxed, but no static
mount was added. Tabler core CSS/JS, Chart.js, and `Quicksand` load from
jsDelivr/fonts.googleapis.com.
- **Inline SVG icon helper.** No Tabler Icons webfont; each page carries a
minimal `icon(name, size)` dictionary to avoid an extra font dependency and
potential FOUT/CSP issues.
## What survived from the original guardrails
- API surface unchanged.
- `escapeHtml()` applied to all interpolated text.
- Chart instances reused where Chart.js is used.
- `saveAllConfig()` posts sequential requests.
- SSE status indicator reflects real connection state.
## What was added beyond the spec
- Atomic config writes with threading.Lock + `.tmp` + `os.replace`
(`admin.py`) plus concurrent-write regression test.
- Four-page navigation shell instead of a single-page dashboard.
- Warnings moved from a card into a navbar bell.
- Quota meter moved from a card into a header chip + modal.
- Verdict mix converted from a Chart.js doughnut to pure HTML/CSS partition
bars (avoids canvas lifecycle issues entirely).
- History split from one chart into five per-metric mini charts.
## Review / process gaps identified from the commit series
1. **Chart-instance assignment bug.** `renderVerdict` initially failed to
assign the created Chart instance back to `verdictChart`, causing repeated
"Canvas is already in use" errors every 30-second poll. The QA evidence
console log contained the error but was missed by the approving pass.
**Lesson: read saved console logs, not just screenshots.**
2. **Commit granularity.** The 8-todo plan specified one commit per todo; the
real work coalesced into fewer larger commits. **Lesson: granular commits
make review and bisection easier.**
3. **Service restart gap.** The atomic-write fix landed on disk but
`llm-router.service` was not restarted, so the running process still used
the old code. **Lesson: after a fix that protects the running service,
restart explicitly.**
4. **Null numeric config fields.** `objective.max_energy_per_request` is
`null` in `config.yaml`; the frontend sends empty string, which
`Optional[float]` rejects. Not data-destructive, but it produces spurious
failure toasts on "Save All".
## Suggested future work
- Extract the repeated cross-page CSS into `admin/frontend/admin.css` to
prevent drift across the four pages.
- Extract the inline `icon()` helper into a shared `admin/frontend/icons.js`
module.
- Null-aware config editor (empty string ↔ null round-trips cleanly).
- Wire `admin_schema.sql` RBAC/audit tables behind authentication.
- Better feedback in `models.html` when the override dropdown is disabled
because a model is already overridden.
---
*Original spec below preserved for context.*