…t a channel that is coming back
The review's RETURN, taken. The card told an appliance operator to wait for something that never
arrives, and the comment beside it argued for the same wrong reason.
WHY IT WAS WRONG, re-derived at source rather than taken from the review:
- `enabled` is `state.control_enabled` = `config.DASHBOARD_CONTROL_ENABLED` (`views.py:353`), a
config constant. Its own comment says "display gating only" — there is no liveness in it, so
"not answering" and "it returns with the channel" are both false.
- The only way an APPLIANCE reaches this branch is the "No login" setup.
`apply_appliance_defaults` (`27-defaults-and-config-validation.sh:26-31`) turns the channel on
when `dashboard.control.enabled` is null AND the dashboard password is non-empty, so an empty
password leaves it off — and that file's own comment calls leaving it off "the honest
resolution", not a fault. Nothing is coming back.
So the card now names the cause the operator can actually act on: no dashboard login was set, the
control channel sits behind one, and Set up again in the boot menu is where a password gets set.
The narrow-branch guarantees are unchanged — no `pithead apply`, no `config.json`, neither of
which an appliance operator can reach.
The comment above the branch is corrected too. It stated the defect as a safety property ("a
quiet channel is a fault to wait out"), which is why the wording read as considered.
Proven:
- MUTATION CONTROL: reverting the copy to the old sentence reddens exactly one test in each of
the two files, so the new assertions are load-bearing rather than vacuous. Restored by sha256.
- Whole-tree sweep for the retired wording comes back with only the two `doesNotMatch` guards
that forbid it, with a control showing the sweep can see this class at all.
- Frontend suite 587 passed rc 0; lint-js (which lints CSS too) rc 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KZuzdtxNX8ae4rnDtBqEqR
Closes part of #1854 — the dashboard half. The wizard restore half follows in a second PR (see Scope below).
What the operator will see change on the appliance
A fourth entry, Backup, in the view toggle beside Simple / Advanced / Configuration. Choosing it shows the backup card on its own; the card is gone from the bottom of the Configuration view — it moved, it did not multiply. The choice is remembered across reloads like the other three.
The card also says two things it did not say before:
dashboard.control.enabled: trueinconfig.jsonand run./pithead apply. On the appliance there is no shell and the control channel is always on, so that is advice its operator cannot act on. There, the card now says backup returns with the control channel. The non-appliance wording is unchanged, and a test holds that branch narrow.Scope — why this is half of #1854
#1854 has a dashboard half (points 1–2: the nav entry and the card copy) and a wizard half (point 3: restore from an uploaded archive + kit passphrase, driven by the host runner). This PR is the dashboard half only. I split it deliberately rather than shipping one large PR:
wizard.css, fix(wizard): #1879 a value with no break opportunity no longer runs past the card #1885) and is budget-constrained —wizard.mjshas 5 lines of headroom and12-firstboot-wizard.shhas zero, so its restore screen needs a new module either way.If the reviewer would rather see them together, say so and I will hold this one.
Tier and evidence — what was RUN
Tier 3 (frontend render) plus tier 1 unit, per the four-tier model. All of this ran locally at
c5fc5d74:node --test dashboard/tests/frontend/make test-dashboardmake lint-js(lints CSS too),lint-md,lint-docs-voice,lint-operator-strings,lint-file-budget,lint-topologyControlled pair on the new tests. A green test proves nothing until it has been shown able to fail, so the eight new tests were run against unmodified
origin/develop(0a4195c9) source with only the test files copied in: 7 of 8 reddened. The eighth is deliberate —BackupPanel off a non-appliance host keeps the remedyis the narrowness control, asserting the pre-existing path is unchanged, so it must pass on both sides.One real defect this found. My first absence assertion used the needle
/off with the rest of the control channel/to prove the Backup card had left the Configuration stack. It failed — because the Diagnostics card beside it says that same sentence about itself. The card had in fact moved correctly; the needle was matching a sibling. Tightened to/Backup export is off .../, and the reason is in the test file so the next person does not re-loosen it.NOT run:
lint-sh(shellcheck OOMs this box, and this change touches no shell), docker builds, KVM, a real browser. CI is the gate for those.Budgets — these decided the design
dashboard.cssis at 1579/1579, zero headroom, so this adds no CSS: every class used (btn-toggle,card,card-stack,text-muted,text-xs) already exists.components.test.mjsis at 1055/1055, so the render/wiring tests go in a newbackupnav.test.mjsreusing the existingharness.mjs— the precedentnodelocation.test.mjsset.components.mjslands at 1147/1177.Shared paths, disclosed
docs/is in_shared.paths. This change falsifies prose naming "Configuration → Backup", so I swept the whole tree for that phrase class rather than fixing the site I happened to open: two sites indocs/appliance.md(434 and 495 — the second is not the one I noticed first), both corrected; the sweep re-runs to 0 hits, having fired at 2 before.docs/dashboard.mdgains a Backup view section. No other lane's paths are touched, and no.lane-overridewas needed or used.Review notes
state.os_updateas the appliance signal is the file's existing convention (ConfigViewalready takesappliance=${!!state.os_update}) — reused, not invented.return htmlblocks in the disabled branch repeat<div class="card"><h3>Backup</h3>. That repetition is this file's idiom —renderKit,renderFailedandrenderall repeat it — so extracting a wrapper here would make the hunk inconsistent with its neighbours. Say if you disagree.developthe seat's--admin.