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
94 lines
4.2 KiB
Markdown
94 lines
4.2 KiB
Markdown
# 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.*
|
||
|