feat(diagnostics): doctor next steps as data, acting chips, consequence-first status copy - #2240
Draft
thinmintdev wants to merge 4 commits into
Draft
thinmintdev wants to merge 4 commits into
thinmintdev wants to merge 4 commits into
Conversation
health_report.py's to_diagnosis() hard-coded next_steps=[] for every one of the 7 hal0-doctor-verify checks (API, mDNS, runners, capability slots, memory engine, OpenWebUI, Hermes), even though the typed Diagnosis.next_steps / NextStep(kind="command"|"manual"|"doc") shape already existed and DiagnosisPanel.jsx already rendered chips for it — so GET /api/doctor and `hal0 doctor verify --json` always answered with an empty list, and a command's remediation text was reachable only via a tooltip. Extend Check with a next_steps field and populate it at the same point each classifier already builds its human-readable detail string, so the exact sub-state that produced the detail also produces the matching remedy (one owner per fact, no drift between what the message says and what the step recommends). Every fail/warn branch now carries at least one NextStep: a literal command (`hal0 serve`, `hal0 slot restart <name>`, `hal0 slot create`, or a `systemctl restart <unit>` for the four repairable companion services), a doc link, or a manual instruction where no single command applies. The auth-posture check's remedy is intentionally left out here — noted in status, not stubbed in code — since it depends on w0a's check landing in the shared classifier first. to_diagnosis() now threads Check.next_steps through instead of discarding it; GET /api/doctor needed no changes since it already calls to_diagnosis() verbatim. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AbAfiRnPCuPwn3E7YMMgan Signed-off-by: thinmintdev <alexander@awideweb.com>
DiagnosisPanel.jsx's next-step chips showed a command only in a `title=`
tooltip and offered no interaction. Every chip now acts on its own kind:
`command` shows the exact command inline in mono with a Copy button
(clipboard.ts, real success/failure toast), plus a Run button when the
command maps onto an existing typed mutation; `doc` opens the target (an
external hal0.dev link or an in-app hash route); `manual` expands the full
instruction inline instead of sitting inert.
next-step-actions.ts is the pure command-string -> action mapping ("Run"
support without inventing new endpoints): the four `systemctl restart
<unit>` commands health_report.py emits map to useServiceRepair (the same
one-click repair RestartApiPanel.jsx already uses, restricted server-side
to installer.py's _REPAIRABLE_UNITS), and `hal0 slot restart <name>` maps
to useSlotRestart (the same mutation the slot card's own Restart button
uses). An unmapped command still copies fine, it just has no Run button —
deliberately not string-sniffing every possible command.
StepsDrawer.jsx is a reusable numbered-steps drawer (eyebrow/title/numbered
rows, same command/doc/manual affordances) that DiagnosisPanel opens via a
new "Steps (N)" button whenever a diagnosis carries two or more next steps;
built generic enough to reuse as-is for a future extension-enable-steps
surface.
Tests: next-step-actions.test.ts (action mapping + doc-target resolution),
a new Playwright spec covering the copy/Run/doc-open/StepsDrawer paths
against a mocked /api/doctor feed. The Playwright spec could not be run on
this dev box — chromium_headless_shell fails to launch here
(libnspr4.so missing, no sudo to install it) and the two pre-existing
diagnostics specs fail identically unmodified, confirming a host gap, not
a regression; CI has the full browser deps.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AbAfiRnPCuPwn3E7YMMgan
Signed-off-by: thinmintdev <alexander@awideweb.com>
Slot lifecycle words (offline, pulling, starting, warming, ready, serving, idle, unloading, error) and companion-service health words (up, stopped, down) are precise but internal — accurate for an operator who already knows the state machine, opaque to one who doesn't. status-copy.ts is the single owner of a one-sentence "what this means for you" line per word, deliberately kept separate from the vocabulary itself: callers keep rendering the precise word unchanged and add this sentence alongside it as a tooltip. Wired into the slot edit drawer's state chip, the slot card's status dot (appended after the existing precise-phase tooltip — window.slotIndicator's return value stays byte-for-byte pinned, only the rendered DOM title gains the extra sentence), and the Services card's status pill. tests/ui_contracts/test_status_copy_mirror.py mirrors tests/ui_contracts/test_flag_aliases_mirror.py's approach: it parses the TS object literal out of status-copy.ts and asserts its keys equal hal0.slots.state.SlotState's members exactly, so a new lifecycle state can't ship without a status line. The service-health words (up/stopped/ down) have no backing Python enum — they're literals in services_health.py — so status-copy.test.ts's completeness check is their whole contract. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AbAfiRnPCuPwn3E7YMMgan Signed-off-by: thinmintdev <alexander@awideweb.com>
Documents the Doctor panel's new acting next-step chips and Steps drawer in docs/guides/dashboard-and-settings.mdx, and adds the CHANGELOG [Unreleased] ### Added entry for the whole feature. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AbAfiRnPCuPwn3E7YMMgan Signed-off-by: thinmintdev <alexander@awideweb.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
health_report.py's 7hal0 doctor verifyclassifiers (API, mDNS,runners, capability slots, memory engine, OpenWebUI, Hermes) hard-coded
next_steps=[]on every row (src/hal0/health_report.py:282before thisPR) even though the typed
Diagnosis.next_steps/NextStep(kind="command"|"manual"|"doc")shape already existed(
src/hal0/diagnostics.py:41-56) andui/src/dash/diagnostics/DiagnosisPanel.jsx:31-44already rendered chips for it — so
GET /api/doctoralways answered withan empty list and a remediation command was reachable only via a
title=tooltip. This PR:
NextStepfor every fail/warn branch each classifiercan reach — the exact command an operator would run (
hal0 serve,hal0 slot restart <name>,hal0 slot create, orsystemctl restart hal0-api|hindsight-api|hal0-openwebui|hal0-agent@hermes), a doc link,or a manual instruction where no single command exists — built at the
same point each classifier already produces its human-readable detail
string, so message and remedy can't drift apart.
DiagnosisPanel.jsx's chips act:commandchips show thecommand inline in mono with Copy, plus Run when it maps onto an
existing typed mutation (
useServiceRepair— the same one-click repairRestartApiPanel.jsxuses, restricted server-side toinstaller.py's_REPAIRABLE_UNITS— oruseSlotRestart);docchips open the link;
manualchips expand.StepsDrawer(numbered steps, same affordances) thatopens for any diagnosis with ≥2 next steps — generic enough to reuse
later for extension enable steps.
status-copy.ts: a one-sentence, consequence-first "what thismeans for you" line for every slot lifecycle state
(
offline/pulling/starting/warming/ready/serving/idle/unloading/error) and companion-service health word(
up/stopped/down), shown as a tooltip beside the existing precisevocabulary in the slot drawer, the slot card's status dot, and the
Services card — the precise words themselves are unchanged.
The auth-posture check's remedy is intentionally not included — it
depends on teammate w0a's check landing in the shared classifier first;
noted as a follow-up, not stubbed in code.
Risk grade
break (
NextStep's 3-field shape is unchanged, existing pinnedtests confirm it)
Touched surfaces
src/hal0/api/routes/doctor.pyunchanged — consumesto_diagnosis()verbatim;src/hal0/health_report.pyis theclassifier module it composes)
ui/src/, Playwright specs)docs/)§14.1 high-risk surfaces
None apply — no new routes, no
AUTONOMOUS_WRITE_TOOLSchanges, noinstaller/updater shell-out.
Rollback
Revert this PR's 4 commits;
Check.next_stepsdefaults to an empty tupleand
to_diagnosis()reverting tonext_steps=[]restores prior behaviorexactly (no data migration, no persisted state).
Test tiers run
uv run pytest tests/ -q -n 8: 12787 passed, 21 skipped, 1xfailed, 3 failed — the same 3 pre-existing failures this dev box
always has (
test_moonshine_server.pyneedsffmpegthe box lacks;test_pressure_eviction.py::test_pressure_evict_noop_when_probe_failsis flaky on this box per the parity program's baseline notes), none
touched by this diff.
uv run ruff check/format,uv run mypy,python3 scripts/check_sunset.pyall clean.in this worktree; nothing in this diff touches the integration
surface — pure classifier logic + UI).
npm run lint && npm run typecheck && npm run test:unit && npm run buildall green (497 unit tests). A new Playwright spec(
diagnostics-next-steps-v3.spec.ts) covers copy-to-clipboard,Run, doc-open, and the StepsDrawer against a mocked
/api/doctorfeed, but could not be executed on this dev box —
chromium_headless_shellfails to launch here(
libnspr4.so: cannot open shared object file, no sudo available toinstall it); the two pre-existing diagnostics Playwright specs fail
identically unmodified, confirming a host-level gap rather than a
regression in this change. CI has full browser deps and should
confirm.
🤖 Generated with Claude Code
https://claude.ai/code/session_01AbAfiRnPCuPwn3E7YMMgan