feat(admin): cockpit quick wins, six admin portal fixes (R5 G1 E3 R2 G3 C1) #101

Merged
alee merged 6 commits from feat/cockpit-quick-wins into main 2026-09-26 07:09:47 +00:00
Owner

Six small admin-portal fixes from the cockpit review. Spec: plans/cockpit-quick-wins.md; executed from .omo/plans/cockpit-quick-wins.md. One commit per item.

# item commit
1 R5: the classifier card shows a disabled loading... state until its config arrives, instead of claiming local_llm on first paint fix(admin): classifier card renders a loading state until config loads
2 G1: the nav links are defined once in navbar.js and rendered into #nav-links on all eight pages refactor(admin): render nav links from navbar.js instead of eight copies
3 E3: the Decisions filters (kind, category, profile, tier, q, size) are read from and written to the URL with replaceState feat(admin): decisions filters read and write the URL
4 R2: the Home Decisions tile counts route_decisions and client_outcome reports only, instead of summing every verification row fix(admin): decisions tile reports route decisions and client outcomes
5 G3: a warning dismissal is keyed on severity plus digit-normalized text, so it survives a count changing; stored keys are migrated fix(admin): warning dismissals key on digit-normalized text and severity
6 C1: one Knobs table (knob, live, persisted, layer) replaces the two side-by-side cards, with an in-row reverts on restart drift badge feat(admin): one knob table merges runtime and persisted state

Notes for review

  • Item 2 moved an invariant. Links now render client-side, so tests/test_admin_nav_is_complete.py asserts the single NAV_LINKS list and the #nav-links mount rather than per-page markup. Two tests that grepped for static nav text were updated the same way.
  • Item 4 adds decision_outcomes to /admin/api/snapshot. metrics.decision_outcome_summary exists for this, and verdict_mix is unchanged: the TUI and the Signal quality card still use it.
  • Item 6 adds a config_key field to each GET /admin/api/runtime item. It is derived from the existing knob registries, not hand-copied. Drift is computed only from that payload's runtime vs persisted, so runtime-only knobs get the badge too, and unsaved persisted edits never count as drift. id="config-list" stays on the table body, so the existing save and dirty-tracking selectors work unchanged. Unsaved edits survive the table's periodic re-render.
  • JS unit tests run through node, in the new tests/test_admin_js_units.py, and skip if node is not installed. The logic is extracted verbatim from the pages between marker comments, not reimplemented in Python.

Verification

  • pytest: 2,283 passed, 1 failed. The failure is tests/test_incumbent_routing.py::TestDebugLog::test_debug_log_emits_incumbent_identity, which fails identically on a clean origin/main (2,255 passed, 1 failed). The branch adds 28 tests and no new failures.
  • Lint: ruff@0.16.9 reports no new findings in any touched .py file compared with origin/main.
  • Visual checks on a throwaway 8081 instance against a copy of the live DB, at 1400 and 800 px. All 18 knob rows render, and the drift badge shows on a runtime-only knob.
  • Rebased onto origin/main (f554fc1). git range-diff shows five commits unchanged; commit 4 differs only in context, from the tests/test_metrics.py append that sits next to #99's tests.

Small nits, not fixed here

  • The kWh unit label wraps onto its own line under its input.
  • At 800 px the Live select for routing.default_flex_preference is truncated.
  • The Live control for routing.default_profile is a free-text input. It was one before this change too.
  • One box-drawing comment header in navbar.js matches that file's existing style but is not ASCII.

🤖 Generated with Claude Code

https://claude.ai/code/session_01N9biTbFC63yDfYfUsZmhgd

Six small admin-portal fixes from the cockpit review. Spec: `plans/cockpit-quick-wins.md`; executed from `.omo/plans/cockpit-quick-wins.md`. One commit per item. | # | item | commit | |---|---|---| | 1 | **R5**: the classifier card shows a disabled `loading...` state until its config arrives, instead of claiming `local_llm` on first paint | `fix(admin): classifier card renders a loading state until config loads` | | 2 | **G1**: the nav links are defined once in `navbar.js` and rendered into `#nav-links` on all eight pages | `refactor(admin): render nav links from navbar.js instead of eight copies` | | 3 | **E3**: the Decisions filters (`kind`, `category`, `profile`, `tier`, `q`, `size`) are read from and written to the URL with `replaceState` | `feat(admin): decisions filters read and write the URL` | | 4 | **R2**: the Home Decisions tile counts `route_decisions` and `client_outcome` reports only, instead of summing every verification row | `fix(admin): decisions tile reports route decisions and client outcomes` | | 5 | **G3**: a warning dismissal is keyed on severity plus digit-normalized text, so it survives a count changing; stored keys are migrated | `fix(admin): warning dismissals key on digit-normalized text and severity` | | 6 | **C1**: one Knobs table (knob, live, persisted, layer) replaces the two side-by-side cards, with an in-row `reverts on restart` drift badge | `feat(admin): one knob table merges runtime and persisted state` | ## Notes for review - **Item 2 moved an invariant.** Links now render client-side, so `tests/test_admin_nav_is_complete.py` asserts the single `NAV_LINKS` list and the `#nav-links` mount rather than per-page markup. Two tests that grepped for static nav text were updated the same way. - **Item 4 adds `decision_outcomes` to `/admin/api/snapshot`.** `metrics.decision_outcome_summary` exists for this, and `verdict_mix` is unchanged: the TUI and the Signal quality card still use it. - **Item 6 adds a `config_key` field to each `GET /admin/api/runtime` item.** It is derived from the existing knob registries, not hand-copied. Drift is computed only from that payload's `runtime` vs `persisted`, so runtime-only knobs get the badge too, and unsaved persisted edits never count as drift. `id="config-list"` stays on the table body, so the existing save and dirty-tracking selectors work unchanged. Unsaved edits survive the table's periodic re-render. - **JS unit tests run through `node`**, in the new `tests/test_admin_js_units.py`, and skip if `node` is not installed. The logic is extracted verbatim from the pages between marker comments, not reimplemented in Python. ## Verification - `pytest`: 2,283 passed, 1 failed. The failure is `tests/test_incumbent_routing.py::TestDebugLog::test_debug_log_emits_incumbent_identity`, which fails identically on a clean `origin/main` (2,255 passed, 1 failed). The branch adds 28 tests and no new failures. - Lint: `ruff@0.16.9` reports no new findings in any touched `.py` file compared with `origin/main`. - Visual checks on a throwaway 8081 instance against a copy of the live DB, at 1400 and 800 px. All 18 knob rows render, and the drift badge shows on a runtime-only knob. - Rebased onto `origin/main` (`f554fc1`). `git range-diff` shows five commits unchanged; commit 4 differs only in context, from the `tests/test_metrics.py` append that sits next to #99's tests. ## Small nits, not fixed here - The `kWh` unit label wraps onto its own line under its input. - At 800 px the Live select for `routing.default_flex_preference` is truncated. - The Live control for `routing.default_profile` is a free-text input. It was one before this change too. - One box-drawing comment header in `navbar.js` matches that file's existing style but is not ASCII. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01N9biTbFC63yDfYfUsZmhgd
alee added 6 commits 2026-09-26 07:07:56 +00:00
Backend:
- metrics.decision_outcome_summary(conn, since_days=7): route_decisions
  count in 7d window + verifications kind='client_outcome' counts
  (total / succeeded / failed), parameterized like verdict_mix.
- admin_snapshot exposes it as "decision_outcomes" (zeros on empty DB).

Frontend (renderBoard Decisions tile):
- One guarded local `dout` (|| defaults) so a stale tab without the
  snapshot key cannot throw and blank the board.
- Headline = decisions in 7 days; qualifier "routed in 7 days".
- Client-outcome line when client_reports > 0:
  "<n> client reports (P%): <ok> ok, <failed> failed" where P is
  guarded against decisions===0 (percent omitted, counts kept) and the
  failed count gets the warn colour when failed/reports > 0.10
  (matching the Signal card's >10% threshold), guarded against
  client_reports===0.
- "no client reports in 7 days" when client_reports===0.
- The word "verified" no longer appears in the tile; renderLive keeps
  verdict_mix untouched.

Tests: unit tests for the summary (window exclusions, client_outcome
scoping, structural verdicts excluded), exhaustive snapshot key +
zeros assertion, top-level key list, and static renderBoard/renderLive
regex locks. Append-only.
alee merged commit 827c408cd9 into main 2026-09-26 07:09:47 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: alee/6krrt#101