fix(admin): label the Classifier card and show what is running vs saved #108

Merged
alee merged 1 commits from fix/admin-classifier-card into main 2026-10-03 05:42:23 +00:00
Owner

What

Fixes the Classifier card on the Controls page, which was hard to read and said things that were not true.

before after
Six local_decision inputs had no labels, only placeholders that vanish once you type Every input in every mode carries a visible <label for>
Header badge read local_decision (overlay) right after a save, though the router was still running local_llm Badge names the running mode; an amber restart pending: <mode> badge appears beside it until the service restarts
Typing 0 into confidence_min or coverage_min was rewritten to the default (value || default) A blank field is left out of the overlay so the repo default keeps floating; a typed 0 is saved as 0
Mode select and field grid centred against each other align-items-start so labels line up across the row
local_encoder threshold placeholder clipped to confidence % (0- Placeholder is 0-100 (the label and % addon already say what it is)

Why the badge was wrong

GET /admin/api/classifier-config returned the saved (merged) config. A save only persists to config.local.yaml, because the router binds cfg.classifier at import. So the card showed the future mode as if it were live. The response now also carries:

  • running_mode: what the process is using.
  • restart_pending: true when the saved classifier block differs from the running one in any field, null when the saved config no longer validates (so the comparison cannot be made). It compares the whole block, not just mode, so a changed decision.confidence_min flags too.

mode keeps its meaning (saved), so existing consumers are unaffected.

One consequence to know about: a decision sub-block left in the overlay while mode is local_llm still shows restart pending: settings changed, although a restart would not change behaviour. I left it that way on purpose. The badge errs toward warning, and a restart makes saved and running equal again.

Deploy note

The frontend is static, so a pull is enough for the labels, 0 handling and layout. The badge needs the new GET fields, so it needs a service restart; until then it falls back to data.mode and behaves as before.

Verification

  • tests/test_admin_classifier_config.py: four new tests (nothing pending when saved matches running; saved mode change flagged and running_mode kept; companion-only change flagged; null when the saved config is invalid).

  • tests/test_admin_frontend.py: three new tests (every card input has a label; the collect path does not default a deliberate zero; the card renders running mode plus the pending badge). The existing pinned data-key="decision.*" tests still pass.

  • Live, in a throwaway 8081 sandbox on this branch: saved local_decision and saw restart pending: local_decision beside local_llm; saved confidence_min: 0 and read back 0 in both the overlay and the GET; switched back to local_llm and saw restart pending: settings changed.

  • Layout measured in the DOM for all four modes at 1044px, and in same-origin iframes at a true 800px and 600px: no element overflows the card, no horizontal scroll, every visible field labelled.

  • Not done: no visual check of the 800px or 600px renderings, because the browser's window resize did not reach the page and the screenshots were unusable at that point. Those two widths are covered by the DOM measurement only.

  • Full suite: 2542 passed, 1 failed. The failure is tests/test_incumbent_routing.py::TestDebugLog::test_debug_log_emits_incumbent_identity, and it fails identically on clean main (cc72f20), so it is not from this branch. Cause: _seed_measured_cache_rate hardcodes observed_at='2026-09-13T00:00:00+00:00', and objective.cache_rate_window_hours is 168, so those rows aged out of the window around 2026-09-20 and the incumbent is priced assumed instead of measured. Confirmed by changing only that timestamp in a scratch export of main: the file goes to 26 of 26. Left for a separate fix (seed relative to now).

🤖 Generated with Claude Code

https://claude.ai/code/session_01KkCGRantZsSwmcFpet6FTa

## What Fixes the Classifier card on the Controls page, which was hard to read and said things that were not true. | before | after | |---|---| | Six `local_decision` inputs had no labels, only placeholders that vanish once you type | Every input in every mode carries a visible `<label for>` | | Header badge read `local_decision (overlay)` right after a save, though the router was still running `local_llm` | Badge names the **running** mode; an amber `restart pending: <mode>` badge appears beside it until the service restarts | | Typing `0` into `confidence_min` or `coverage_min` was rewritten to the default (`value \|\| default`) | A blank field is left out of the overlay so the repo default keeps floating; a typed `0` is saved as `0` | | Mode select and field grid centred against each other | `align-items-start` so labels line up across the row | | `local_encoder` threshold placeholder clipped to `confidence % (0-` | Placeholder is `0-100` (the label and `%` addon already say what it is) | ## Why the badge was wrong `GET /admin/api/classifier-config` returned the **saved** (merged) config. A save only persists to `config.local.yaml`, because the router binds `cfg.classifier` at import. So the card showed the future mode as if it were live. The response now also carries: - `running_mode`: what the process is using. - `restart_pending`: `true` when the saved `classifier` block differs from the running one in any field, `null` when the saved config no longer validates (so the comparison cannot be made). It compares the whole block, not just `mode`, so a changed `decision.confidence_min` flags too. `mode` keeps its meaning (saved), so existing consumers are unaffected. One consequence to know about: a `decision` sub-block left in the overlay while `mode` is `local_llm` still shows `restart pending: settings changed`, although a restart would not change behaviour. I left it that way on purpose. The badge errs toward warning, and a restart makes saved and running equal again. ## Deploy note The frontend is static, so a pull is enough for the labels, `0` handling and layout. The **badge** needs the new GET fields, so it needs a service restart; until then it falls back to `data.mode` and behaves as before. ## Verification - `tests/test_admin_classifier_config.py`: four new tests (nothing pending when saved matches running; saved mode change flagged and `running_mode` kept; companion-only change flagged; `null` when the saved config is invalid). - `tests/test_admin_frontend.py`: three new tests (every card input has a label; the collect path does not default a deliberate zero; the card renders running mode plus the pending badge). The existing pinned `data-key="decision.*"` tests still pass. - Live, in a throwaway 8081 sandbox on this branch: saved `local_decision` and saw `restart pending: local_decision` beside `local_llm`; saved `confidence_min: 0` and read back `0` in both the overlay and the GET; switched back to `local_llm` and saw `restart pending: settings changed`. - Layout measured in the DOM for all four modes at 1044px, and in same-origin iframes at a true 800px and 600px: no element overflows the card, no horizontal scroll, every visible field labelled. - Not done: no visual check of the 800px or 600px renderings, because the browser's window resize did not reach the page and the screenshots were unusable at that point. Those two widths are covered by the DOM measurement only. - Full suite: **2542 passed, 1 failed.** The failure is `tests/test_incumbent_routing.py::TestDebugLog::test_debug_log_emits_incumbent_identity`, and it fails identically on clean `main` (`cc72f20`), so it is not from this branch. Cause: `_seed_measured_cache_rate` hardcodes `observed_at='2026-09-13T00:00:00+00:00'`, and `objective.cache_rate_window_hours` is 168, so those rows aged out of the window around 2026-09-20 and the incumbent is priced `assumed` instead of `measured`. Confirmed by changing only that timestamp in a scratch export of `main`: the file goes to 26 of 26. Left for a separate fix (seed relative to now). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01KkCGRantZsSwmcFpet6FTa
alee added 1 commit 2026-10-03 05:40:13 +00:00
The card's inputs had placeholders but no labels, its badge reported the
saved mode as if it were live, and a typed 0 was rewritten to the default.

- Every input in every mode carries a visible label; fields align to the top.
- GET /admin/api/classifier-config now returns running_mode and
  restart_pending alongside the saved mode. restart_pending compares the whole
  classifier block, and is null when the saved config no longer validates.
- The header badge names the running mode; an amber "restart pending" badge
  shows until the service restarts, since a save only persists the overlay.
- local_decision collect omits blank fields so repo defaults keep floating,
  and no longer turns a deliberate 0 into the default.
- local_encoder threshold placeholder shortened so it is not clipped.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkCGRantZsSwmcFpet6FTa
alee merged commit 63b659e199 into main 2026-10-03 05:42:23 +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#108