fix(admin): label the Classifier card and show what is running vs saved #108
Reference in New Issue
Block a user
Delete Branch "fix/admin-classifier-card"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Fixes the Classifier card on the Controls page, which was hard to read and said things that were not true.
local_decisioninputs had no labels, only placeholders that vanish once you type<label for>local_decision (overlay)right after a save, though the router was still runninglocal_llmrestart pending: <mode>badge appears beside it until the service restarts0intoconfidence_minorcoverage_minwas rewritten to the default (value || default)0is saved as0align-items-startso labels line up across the rowlocal_encoderthreshold placeholder clipped toconfidence % (0-0-100(the label and%addon already say what it is)Why the badge was wrong
GET /admin/api/classifier-configreturned the saved (merged) config. A save only persists toconfig.local.yaml, because the router bindscfg.classifierat 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:truewhen the savedclassifierblock differs from the running one in any field,nullwhen the saved config no longer validates (so the comparison cannot be made). It compares the whole block, not justmode, so a changeddecision.confidence_minflags too.modekeeps its meaning (saved), so existing consumers are unaffected.One consequence to know about: a
decisionsub-block left in the overlay whilemodeislocal_llmstill showsrestart 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,
0handling and layout. The badge needs the new GET fields, so it needs a service restart; until then it falls back todata.modeand behaves as before.Verification
tests/test_admin_classifier_config.py: four new tests (nothing pending when saved matches running; saved mode change flagged andrunning_modekept; companion-only change flagged;nullwhen 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 pinneddata-key="decision.*"tests still pass.Live, in a throwaway 8081 sandbox on this branch: saved
local_decisionand sawrestart pending: local_decisionbesidelocal_llm; savedconfidence_min: 0and read back0in both the overlay and the GET; switched back tolocal_llmand sawrestart 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 cleanmain(cc72f20), so it is not from this branch. Cause:_seed_measured_cache_ratehardcodesobserved_at='2026-09-13T00:00:00+00:00', andobjective.cache_rate_window_hoursis 168, so those rows aged out of the window around 2026-09-20 and the incumbent is pricedassumedinstead ofmeasured. Confirmed by changing only that timestamp in a scratch export ofmain: 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