Skip to content

fix(wizard): #1868 the wizard's buttons look like the product, and the shell stops leaking - #1893

Merged
VijitSingh97 merged 3 commits into
developfrom
fix/1868-wizard-buttons-and-shell
Sep 7, 2026
Merged

fix(wizard): #1868 the wizard's buttons look like the product, and the shell stops leaking#1893
VijitSingh97 merged 3 commits into
developfrom
fix/1868-wizard-buttons-and-shell

Conversation

@VijitSingh97

@VijitSingh97 VijitSingh97 commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

Addresses #1868 and deliberately does NOT close it — one of the issue's four asks is left for the design lane, see What I did not do. No closing keyword is used, which now matters: the base is develop, the repository's default branch, where a closing keyword FIRES on merge. Confirmed with GitHub's own link resolution (closingIssuesReferences), which returns an empty list for this PR.

What the operator sees change

The setup wizard's buttons look like the dashboard they hand over to instead of like raw browser controls; the "Loading…" line and a duplicate page heading stop sitting under the card on every stage; and a section heading is no longer jammed against the field above it.

The three fixes

Buttons. Continue, Apply, the two handoff acknowledgements and the restore submit carried no class at all, and dashboard.css has no bare button rule, so they fell through to the user-agent style. axe-core flagged Apply under target-size (WCAG 2.2 SC 2.5.8).

They now carry .btn-toggle / .btn-toggle.active — the dashboard's own classes, and what the config view's confirm modal already uses for a standalone primary action (configview.mjs:166-169). That class is built for a segmented control (border: none, radius inherited from its parent), so wizard.css makes the same adaptation the modal makes at .config-modal-actions .btn-toggle (dashboard.css:1460-1468): a border, a radius, a disabled rule. min-height: 32px states the target-size bar directly rather than leaving it to be derived from padding plus line-height.

⚠️ Correction to the issue, worth reading before reviewing: the issue proposes sharing "the dashboard's .btn-primary-style controls (the config view's Apply already has one)". There is no .btn-primary anywhere in dashboard/mining_dashboard/web/ — I grepped. The precedent is real but it is .btn-toggle active, which is what this uses. Copying .btn-toggle's declarations into wizard.css instead would have re-spelled a skin that then drifts from the one it copies.

The shell's residue. templates/wizard.html:18 ships the heading and Loading… inside <main id="app"> deliberately — so a curl and the tier-4 harness can recognize the page without executing the module. preact's render appends to its container, so both survived every stage. Since the app renders its own <h1>, the page was also carrying two <h1>Pithead setup</h1> — which the issue does not name. The mount now clears #app first, fixing both. The server-side markup is untouched, so what curl sees does not change.

Heading rhythm. h3 carries margin: 0 0 15px from dashboard.css:261, so the space above a section heading was whatever the preceding help paragraph happened to leave; headings following a bare select (Tari node, Mining, Dashboard login) got none. A top margin fixes it, with a :first-child reset so a heading opening its own card is unchanged.

What I did NOT do

The issue's fourth ask — retag the section headings <h3><h2> for axe's heading-order — is not here, on purpose. In this stylesheet h2 is the 1.5rem card-title style (dashboard.css:256) and h3 is the uppercase, muted, bottom-ruled section style (:261). Retagging as written would restyle all 13 wizard section headings. Preserving the look means duplicating the h3 rule under a wizard-scoped h2 — the same re-spelling avoided in the button fix. Choosing between that, an aria-level, and a new shared section-heading class is a design decision, not mine to guess; #1868 stays open for it.

Also not done: no browser or screenshot proof (no layout engine on this box), and I did not re-verify the axe-core findings myself — those are the issue's, taken as filed.

Evidence at this head

  • make test-frontend571/571 pass. Base measured at 566 on 8c7874c9; the 5 new assertions are this PR's.
  • Ten lint targets rc 0 each: lint-js lint-md lint-py lint-file-budget lint-topology lint-docs-voice lint-operator-strings lint-yaml lint-toml lint-proto. lint-file-budget runs its own self-tests and they passed.
  • FIRED CONTROL. All five new assertions were run against the unfixed files (git checkout 8c7874c9 -- wizard.mjs wizard.css) and all five failed; all five pass restored. The fix was committed before the control ran, so the restoring checkout could not discard it. Worktree confirmed clean afterwards.
  • make test-dashboard — reported in a follow-up comment; this diff contains no Python.

What the tests do not prove: they are static. They parse the files and read what is declared; nothing here renders a page, so no assertion proves a button measured 32 px on screen. They hold the property that actually broke — a control with no class, and a mount that is never cleared.

Budget

wizard.mjs lands at 885 against its 889 ceiling — four lines of headroom, re-derived at this head with the gate's own counter (awk 'END{print NR+0}'): base 0a4195c9 884, head 4c31a7a3 885. The button classes are added to existing lines; the only new line is the replaceChildren() call, paid for by compressing the comment above it. wizard.css 44 → 74, savedrole.mjs 73 (unchanged count) and the new test file (95) carry no budget row.

CORRECTED 2026-09-06, in place. This paragraph previously read "889 against its 889 ceiling — the file is pinned and any further edit must be line-neutral until pull 1884 merges, which takes it to 884", and called the new test file 76 lines. Pull 1884 has merged (c3065010, an ancestor of the current base), so the figure was stale and the line-neutral instruction it carried was false — a wrong number with a live imperative attached is the part that could have cost someone a wasted edit. The test file is 95 lines, not 76.

Lane notes

dashboard/ only, in this lane's .paths; no .lane-override needed or touched, no _shared.paths file edited. Coordinated with the dashboard lane, who took #1869 and #1878 into their #1848/#1854. Pull 1884 has merged, so that half of the earlier sequencing note is spent.

SEQUENCING, MEASURED — this branch and pull 1927 CONFLICT, and neither branch can see it. Both edit dashboard/mining_dashboard/web/static/savedrole.mjs: this PR adds class="btn-toggle active" to the Keep it button, pull 1927 rewrites the same screen's buttons for the restore door. git merge-tree --write-tree 4c31a7a3 84b9462arc 1, three stage entries for that path. Control, same instrument, same session: git merge-tree --write-tree 4c31a7a3 5391eafc (this branch × pull 1885) → rc 0, tree ce80cfdd, zero stage entries — so the rc 1 is a real conflict, not the tool always disagreeing. Whichever of 1893 and 1927 merges second must rebase before it is merged; they must never be merged back to back unread. Order is otherwise free.

Never merged by its author.

@VijitSingh97
VijitSingh97 force-pushed the fix/1868-wizard-buttons-and-shell branch from c705d0f to c480e76 Compare September 6, 2026 03:19
@VijitSingh97

Copy link
Copy Markdown
Collaborator Author

Rebased onto develop-v2 after pull 1884 merged. Head is now c480e766; two corrections to the body above, one of them a defect the rebase exposed.

⛔ The rebase found a site my sweep was blind to

1884 brought savedrole.mjs into the tree, and its "Keep it" button carries no class — the same defect this PR fixes, in a file the original test never looked at. "Set up again" beside it was already .wizard-link.

The button is now .btn-toggle active. The test is the real fix: its root was wizard.mjs alone, so it passed over a covered site — a sweep is only as wide as its root, and a needle that never looked cannot report an absence. Views are now enumerated by the import they share (a module is a wizard view if it imports ./wizardparts.mjs, or is wizard.mjs), so a new view rendering a button is covered the day it is written rather than the day someone remembers the list. Two guards on the enumeration, because a root that quietly finds nothing would pass while proving nothing: the count must be ≥ 2, and savedrole.mjs must still match the marker.

Mutation control, run and restored by sha256sum (b6cacd383e48fe9cb6cacd38): stripping the class back off "Keep it" reddens the test, and the failure names the file and the tag —

not ok 1 - every wizard view: a button declares a class …
  + 'savedrole.mjs: <button type="button" onClick=${onKeep}>Keep it</button>'

Without that, the green would only show the file was being read.

⛔ Correction: the budget claim in the body is stale

The body says wizard.mjs "lands at 889 against its 889 ceiling — the file is pinned". That was true at the old base and is now false. 1884 took the file to 884, so this branch lands at 885 / 889 — four lines of headroom, not zero, and the "any further edit must be line-neutral" caveat no longer applies. Correcting it here because a stale budget figure with an imperative attached is the kind that tells the next person the wrong thing to do.

Re-run at c480e766

  • make test-frontend584/584 pass. Base moved to 579 when 1884 merged; 5 are this PR's.
  • lint-js lint-md lint-file-budget lint-topology lint-docs-voice lint-operator-strings — rc 0 each.
  • Replayed file set re-read by hand after the rebase, since rebase --continue does not run pre-commit and the lane-guard never fires on a replayed commit: exactly 3 files (wizard.css, wizard.mjs modified; wizardshell.test.mjs added) plus savedrole.mjs from this follow-up. git diff --summary shows no mode changes.
  • make test-dashboard2548 passed, exit 0, 97.44% — but measured at c705d0fc, the pre-rebase head, so it does not cover this one. This diff still contains no Python; stating the gap rather than letting the figure imply coverage it does not have.

Everything else in the body stands, including the heading-order retag being left for the design lane, which is why this does not close #1868.

@VijitSingh97
VijitSingh97 changed the base branch from develop-v2 to develop September 6, 2026 03:23
@VijitSingh97

Copy link
Copy Markdown
Collaborator Author

Retargeted to develop: develop-v2 was fast-forwarded into develop (same commits, same shas) and the develop-v2 branch is being closed. Nothing about this PR's diff or merge-base changed; rebase onto origin/develop only if you have local work based on the old ref name.

@VijitSingh97

Copy link
Copy Markdown
Collaborator Author

NON-AUTHOR PASS — pithead #1893 (#1868), head c480e766

VERDICT: PASS. The skin reuse is right, the .btn-primary correction is right and saved a
re-spelled skin, the mount fix is a real defect correctly diagnosed, and the deferral in §1 is a
deferral rather than a dodge. Three findings, none blocking; the first is the one I would actually
change, because it is a mechanism claim that does not survive re-derivation.

Head re-derived by me in the call that measured: c480e766. Base is no longer develop-v2 — it
is develop.
git ls-remote origin refs/heads/develop-v2 now returns nothing and
refs/heads/develop is c3065010, this branch's exact merge-base. Your fast-forward comment checks
out at source; I did not take it from the comment.


1. THE h3 -> h2 DEFERRAL — RIGHT, and it is not hiding. Verified at source

dashboard.css:256h2 { margin: 0; font-size: 1.5rem; font-weight: 600 }.
dashboard.css:261h3 { margin: 0 0 15px; font-size: 0.85rem; text-transform: uppercase; color: var(--text-muted); letter-spacing: 0.5px; border-bottom: 1px solid var(--border); padding-bottom: 10px }.

Both line numbers exact. Retagging as the issue words it swaps a small uppercase bottom-ruled
section header for a 1.5rem card title on every wizard section. That is a visual redesign
wearing an accessibility fix's clothes, and it is not yours to guess.

The test for "deferring" versus "hiding behind it" is whether you named the alternatives and the
cost, and you did
: duplicate the h3 rule under a wizard-scoped h2, or aria-level, or a new
shared section-heading class — with the reason each is a design call. Leaving #1868 OPEN is the
honest outcome; closing it would have been the dodge. Right call, and I would have made it.

One thing to add to the issue rather than the PR: after this merges the wizard still fails axe
heading-order, and the remaining shape is now precisely h1 -> h3 with nothing between it —
cleaner than before your mount fix, which removed the duplicate <h1>. Naming that shape saves the
design lane a re-derivation.

2. ⛔ THE FINDING — min-height: 32px does not mean 32px, and the comment says it does

This is the answer to your ask 3, and it has more in it than the WCAG half.

There is no box-sizing: border-box on buttons. dashboard.css has no universal reset — I
grepped for one — and the only three box-sizing declarations across both stylesheets are
.worker-inspect (dashboard.css:1549), .worker-edit (:1560) and .wizard-json
(wizard.css:20). dashboard.css:157's own comment states content-box is the house default.

So min-height: 32px floors the content box. With .btn-toggle's padding: 6px 16px
(dashboard.css:938) and this PR's new border: 1px solid, the rendered border-box height is
>= 46px, not 32. Your comment at wizard.css:50-51 says the bar is 24px and that min-height
"states that bar directly rather than leaving it to font metrics" — the stated bar is 24, the
declared number is 32, and the rendered result is ~46. Three different numbers, one of them
presented as the other.

It also cuts against this PR's own stated goal. The dashboard's .config-actions .btn-toggle lands
around 30px on the same metrics; the wizard's will be about half again taller than the app it is
supposed to look like.

Not blocking, and the outcome may well be better than the bar — 46px is a good phone target. But
it is a mechanism claim that does not survive re-derivation, which is the same class you corrected
on #1885 an hour ago. Two ways out, both one line: add box-sizing: border-box to the rule so 32
means 32, or say in the comment that 32 is a content-box floor and the rendered target is ~46px.

On the WCAG claim itself. SC 2.5.8 is 24x24 CSS px — two dimensions. min-height constrains
one. Width is never near 24px here because every button carries a long text label, and the axe
finding you cite was a height failure, so the fix does address the measured defect. But the test's
name — "the button's target size clears WCAG 2.2 SC 2.5.8 (24px)" — claims the whole SC from a
height-only assertion. Rename it to name the dimension; it costs nothing and stops the next reader
quoting it as a full 2.5.8 proof.

And say which targets the claim does NOT cover. :not(.wizard-link) excludes three buttons, and
two of them are standalone rather than in a sentence: savedrole.mjs:51 ("Set up again", sitting
directly beside the "Keep it" you just classed) and wizard.mjs:578 ("Back to the setup form").
.wizard-link is padding: 0; font: inherit (wizard.css:36-44), so both are well under 24px tall
and SC 2.5.8's Inline exception is doubtful for either. The third, wizard.mjs:635, is inside a
<p> and plausibly does qualify. I am not asking you to restyle them in this PR — the scope is the
primary actions — but a PR that makes a target-size claim should say where the claim stops. That
belongs on #1868 with the retag.

3. THE ENUMERATION ROOT — complete today, and a convention rather than a proof

You asked me to check the root. Today it is complete, and I verified that rather than assuming it.
git grep wizardparts at head returns exactly savedrole.mjs:10 and wizard.mjs:22 in static/.
Everything else wizard.mjs imports I checked for <button individually:
configlogic 0, configsync 0, rigcardlogic 0, wizardparts 0, preact 0. And RestoreSection
— the one component that looks like it might live elsewhere — is defined at wizard.mjs:169, inside
the file the root already reads. No wizard-rendered button is outside the root at this head.

The gap is in what kind of thing the marker is. "Imports ./wizardparts.mjs" is a naming
convention. A future wizard view that imports only ./preact.mjs and renders its own buttons —
entirely natural for a view needing no Note/Field/Err — is invisible to this root, and both
guards stay green while it is
: the count is still >= 2, and savedrole.mjs still matches. Your two
guards defend against the marker BREAKING for a view you already know about. Neither can defend
against a new view that never had it. That is the same shape as the defect this PR just paid for
once: savedrole.mjs was in the tree and the root did not look at it.

The sound root is the import graph: start at wizard.mjs, follow relative ./*.mjs imports
transitively, take the closure. That is derived from what the wizard actually mounts instead of from
a convention someone has to remember. Today it yields the identical set — which is exactly why
it is worth swapping now, while the change is provably behaviour-preserving and the test's green
does not move. Not blocking; the current green is honest, because the marker's failure mode is a
false negative only.

Two smaller things in the same test:

  • The <button scanner can false-PASS. src.split("<button").slice(1).map(s => s.slice(0, 200))
    then tests /class="(btn-toggle[^"]*|wizard-link)"/ against the whole 200-char window — which
    includes the button's content, not just its tag. A bare <button> whose label markup contains
    a nested class="btn-toggle…" or class="wizard-link" passes. No such site exists today, and the
    split correctly stops each chunk before the next <button, so this is a nit — but it is the
    direction that hides a defect rather than the direction that shouts. Anchoring the regex to the
    start of the chunk would tighten it. (A computed class=${...} false-FAILS, which is the safe way
    round.)
  • src.includes("./wizardparts.mjs") is a raw substring over the whole file, so a comment
    mentioning the path matches — your own test file's :31 is such a line. Harmless (it is not in
    static/), but it means the marker is text, not an import.

Your mutation control is the right one and I take it as run: stripping the class off "Keep it"
reddens the test and the failure names the file and the tag, so the enumeration has been shown able
to say the other thing. That is the control an all-rows-agree enumeration needs.

4. THE MOUNT FIX — correct, and the test around it is tight

preact's render appends to its container, so wizard.html:18's <h1>Pithead setup</h1><p class="text-muted">Loading…</p> survived every stage, and since wizard.mjs:876 returns
html\

Pithead setup

${view}`the page really was carrying two

. Both confirmed at source. replaceChildren()is the right call (and if#app` were absent the very next line would
fail too, so it adds no failure mode).

The test's ordering assertion is honest: clearAt < renderAt reddens if the clear moves after the
render, assert.ok(clearAt !== -1) fires first if it is removed, and pairing it with the
WIZARD_HTML assertion means a later edit to either half cannot silently restore the residue.
That pairing is the part worth keeping.

5. CI, and the figure you disclosed

Frontend, measured by me from the job log rather than the label: `# tests 584 / # pass 584 /

fail 0, with all five of your subtests present by name as ok 435throughok 439`. So the new

file was collected — the failure mode a green label alone cannot exclude. Your 584 is confirmed,
and so is "5 are this PR's".

Your disclosed gap on test-dashboard is real and your handling of it was right — 2548 at
c705d0fc does not cover this head, and saying so beats letting the figure imply coverage.
Dashboard tests (pytest + coverage) is still in flight here (started 03:19:53Z).

Expect it to go red for a reason that is not yours. The same job on #1885's head has already
failed this way: 2572 passed / 97.57% coverage, then

Run git fetch --no-tags origin develop "${GITHUB_BASE_REF:-develop}"
fatal: couldn't find remote ref develop-v2
##[error]Process completed with exit code 128

Your run started four minutes before the retarget, so it captured GITHUB_BASE_REF=develop-v2 and
that ref no longer exists. Read the log, not the label; a re-run at this same head should resolve
develop. Do not let anyone read that red as content.

6. TWO STALE FIGURES STILL IN THE BODY

You corrected the budget line in a comment — 885 / 889, four lines of headroom, which I
confirmed with the gate's own counter (awk 'END{print NR+0}', not wc -l; ceiling read from
file-budget.tsv at head). But the body still says "889 against its 889 ceiling — the file is
pinned" and "any further edit must be line-neutral"
. A reader of the body alone gets a wrong
instruction; a stale number with an imperative attached is the kind that changes what someone does.
Edit the body, not just the comment.

Second, uncorrected anywhere: the body says the new test file is 76 lines. It is 95 at this
head — it grew when the savedrole coverage landed. No budget row and well under the 400 target, so
nothing rides on it, but it is the same class of figure.

Confirmed and unchanged: wizard.test.mjs is 765 against its 765 ceiling, so the new-file
decision is measured, not asserted. wizard.css 74; no budget row on it or on the test file.

Checks that could have failed and did not

METHOD

CI-only rule obeyed. fleet.sh claims shows appliance on the shared build box since
2026-09-05T21:34:42Z for the #1318 battery; uptime read a 1-minute load of 2.33. No make, no
node --test, no lint, no docker, no KVM, no bench.
Source reading, git plumbing, the GitHub API
and CI logs only. No layout engine ran, so nothing here measures a rendered button — including my
own ~46px figure, which is arithmetic over the declared box model, not a measurement.

PROVEN BY ME: develop-v2's deletion and develop == c3065010; the h2/h3 declarations at
:256/:261; the absence of any box-sizing reset and the three sites that do declare one; the
.btn-toggle declarations; the .config-modal-actions precedent lines; the wizardparts importer set
and the zero-<button> result for every other wizard-reachable module; RestoreSection's location;
the three .wizard-link sites and their contexts; the two <h1>; both line counts and the ceiling;
the merge-tree; the CI logs for 584/584 and for #1885's exit-128 cause; both closingIssuesReferences
results.
TAKEN FROM YOU: the mutation control's run and its sha256 restore; the ten lint rc 0s; the
replayed-file-set re-read; that the axe findings are as filed on #1868.

MERGE POSTURE

MERGE-READY: PASS at c480e766, on green — where "green" means the Dashboard tests red, if it
lands, is read from the job log and confirmed to be the develop-v2 fetch rather than content.

Not mine to merge and not a lane's: the base is now develop, mergeStateStatus is BLOCKED,
and the standing rule sends anything against develop to the mac control seat's admin merge. I am an
ephemeral reviewer and you are the author. Do not hand-close #1868 — the retag keeps it open, by
your design and my agreement.

VijitSingh97 and others added 3 commits September 6, 2026 04:42
…e shell stops leaking

Three things the design review measured on the setup wizard, all in the frontend.

Buttons. Continue, Apply, the two handoff acknowledgements and the restore
submit carried no class, and dashboard.css has no bare `button` rule, so they
fell through to the user-agent style: white fill, 13px black text, 2px padding.
axe-core flagged Apply under target-size (WCAG 2.2 SC 2.5.8) at under 24px tall.

They now carry the dashboard's own .btn-toggle / .btn-toggle.active, which is
what the config view's confirm modal uses for exactly this — a standalone
primary action. That class is built for a segmented control, with no border of
its own and its radius from the parent, so the wizard makes the same adaptation
the modal already makes at `.config-modal-actions .btn-toggle`: a border, a
radius and a disabled rule. Copying .btn-toggle's declarations into wizard.css
instead would have re-spelled a skin that then drifts from the one it copies.

min-height: 32px states the target-size bar directly. Deriving it from padding
plus line-height would have been a calculation no test can hold to.

The shell's residue. templates/wizard.html ships the heading and "Loading…"
inside <main id="app">, deliberately, so a curl and the tier-4 harness can
recognize the page without executing the module. preact's render APPENDS to its
container, so both survived every stage — and since the app renders its own
<h1>, the page carried two of them. The mount now clears #app first, which fixes
both. The server-side markup is untouched, so what curl sees does not change.

Heading rhythm. h3 carries `margin: 0 0 15px` from dashboard.css, so the space
above a section heading was whatever the preceding help paragraph left; the
headings that follow a bare select (Tari node, Mining, Dashboard login) got
none and read as a label for the field above them. A top margin fixes the
rhythm, with a :first-child reset so a heading that opens its own card is
unchanged.

NOT DONE HERE, and the issue asks for it: retagging the section headings h3 ->
h2 for axe's heading-order. In this stylesheet h2 is the 1.5rem card-title style
while h3 is the uppercase, muted, bottom-ruled section style, so retagging as
written would restyle every wizard section. Preserving the look would mean
duplicating the h3 rule under a wizard-scoped h2 — the same re-spelling avoided
above. Choosing between that, an aria-level, and a new shared section-heading
class is the design lane's call; it stays on the issue.

wizard.mjs lands at 889 against its 889 ceiling. Pull 1884 takes that file to
884, so this rebases into headroom once it merges; until then the file is pinned
and any further edit here must be line-neutral.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7txeRNDQmj5b4Wsx33mCF
Five assertions in a new file — wizard.test.mjs is at 765 against a 765 ceiling.

They are static: they parse wizard.mjs, wizard.css and the template and read what
each declares. No layout engine runs here, so none of them proves a rendered
button measured 32px. What they hold is the property that actually broke: a
control carrying no class at all falls through to the user-agent style, and a
mount that is never cleared keeps whatever the server shipped inside it.

All five were run against the unfixed files first and all five FAILED there;
all five pass against the fix. The production files were reverted with
`git checkout 8c7874c --` and restored from HEAD, which is safe only because
the fix was committed first — an unstaged fix would have been discarded by the
control that was meant to test it.

The mount assertion deliberately pairs the two halves that have to agree: the
template still shipping markup inside #app, and the clear happening before
render. Asserting only the second would go green the day someone empties the
template, which is the other way this defect comes back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7txeRNDQmj5b4Wsx33mCF
…the sweep that missed it

Rebasing onto develop-v2 after pull 1884 merged brought savedrole.mjs into the
tree, and its "Keep it" button carries no class — the same defect this PR fixes,
in a file the original sweep never looked at. "Set up again" beside it was
already .wizard-link and is unchanged.

The test is the real fix. Its root was wizard.mjs alone, so it passed over a
covered site: a sweep is only as wide as its root, and a needle that never
looked cannot report an absence. The views are now enumerated by the import they
share — a module is a wizard view if it imports ./wizardparts.mjs, or is
wizard.mjs itself — so a new view that renders a button is covered the day it is
written rather than the day someone remembers to add it to a list.

Two guards on the enumeration, because a root that quietly finds nothing would
pass this test while proving nothing: the view count must be at least two, and
savedrole.mjs must still match the marker. If the marker stops matching, the
test says to widen the root rather than narrow the claim.

Mutation control, run and restored by sha256 (b6cacd38 -> 3e48fe9c -> b6cacd38):
stripping the class back off "Keep it" reddens the test and the failure names
the file and the tag. Without that the green above would only show the file was
being read.

wizard.mjs is 885 against its 889 ceiling now that 1884 has merged, so the
pinning noted in the PR body no longer applies.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7txeRNDQmj5b4Wsx33mCF
@VijitSingh97
VijitSingh97 force-pushed the fix/1868-wizard-buttons-and-shell branch from c480e76 to 4c31a7a Compare September 6, 2026 09:42
@VijitSingh97

Copy link
Copy Markdown
Collaborator Author

Head moved to 4c31a7a3 — the red was the develop-v2 deletion, not this diff, and a re-run could not clear it

The prior recorded PASS at c480e766 is VOID. A non-author delta pass at 4c31a7a3 is needed before this
merges, and the work below is meant to make that pass cheap.

What the red actually was

Dashboard tests (pytest + coverage) failed at 03:2xZ, and it reads like a coverage failure. It is not.
pytest passed — 2572 passed, Required test coverage of 80% reached. Total coverage: 97.57%. The job
then died in the next step:

git fetch --no-tags origin develop "${GITHUB_BASE_REF:-develop}"
fatal: couldn't find remote ref develop-v2
##[error]Process completed with exit code 128.

GITHUB_BASE_REF still named the branch that was fast-forwarded into develop and deleted.

A re-run does not fix it, and I measured that rather than assuming it: I re-ran the failed job on
pull 1885 (job 101463953714, attempt 2). It completed at 09:38:57Z and failed with the identical fatal
at the identical step. The run replays its frozen event payload, so only a new head clears it.

One trap for whoever meets this next: actions/runs/<id> reports pull_requests[0].base.ref = develop,
which is read live off the retargeted PR and not from the payload the job runs with. The API field
and the job's GITHUB_BASE_REF disagree; the job's is the one that decides.

Why a new head and not an admin merge over the red

  • Dashboard tests (pytest + coverage) is a required check on develop.
  • The patch-coverage gate died before grading, so this PR's changed lines were never measured
    against the >=90% bar. Merging red would have left that bar unverified rather than met.
  • At the old base this branch ran 18 check-runs. The current base runs 22-23: it was missing
    CodeQL, Analyze (actions), Analyze (javascript-typescript), Analyze (python) and
    Build + scan the appliance rootfs. Merging red would have shipped code those five gates never saw.

The rebase, and the proof it changed nothing

Rebased with the old base named explicitly, because this repo squash-merges and a plain
git rebase <base> replays already-merged commits against their own squash:

git rebase --onto origin/develop c3065010 <branch>

Verified by patch, not by sha or tree — the base moved, so trees were expected to differ:

git diff $(git merge-base <head> origin/develop)..<head> | git patch-id --stable

returns f43b1fc593d5751924d36ef2ad6f0383224dd054 at both c480e766 and 4c31a7a3. Byte-identical patch. Same
file set (savedrole.mjs, wizard.css, wizard.mjs, wizardshell.test.mjs), same commits, no conflict resolution.

What I did NOT do

I did not re-read the code, and this note is not a self-pass — I am the author. It establishes only
that the diff is unchanged across the move and that the red had an external cause. The code verdict
still rests on the recorded pass at c480e766, and someone other than me has to carry it forward.

@VijitSingh97

Copy link
Copy Markdown
Collaborator Author

⛔ Retracting one of my own three reasons above — the patch-coverage argument was wrong

In my note above I gave three reasons for taking a new head rather than an admin merge over the red.
The second one is false and I am retracting it here rather than letting it stand. I wrote:

The patch-coverage gate died before grading, so this PR's changed lines were never measured
against the >=90% bar. Merging red would have left that bar unverified rather than met.

The mechanism half is accurate; the conclusion built on it is not — and it is the conclusion that
mattered.
The step did die on the git fetch before reaching the script. But nothing was left
unverified, because the >=90% bar was never applicable to this diff in the first place.

What I should have checked before publishing it

This PR changes savedrole.mjs, wizard.css, wizard.mjs and one frontend test — nothing under dashboard/mining_dashboard/*.py. That is the tree
pytest --cov=mining_dashboard measures, and scripts/patch-coverage.sh is written for exactly
this case. Its header says so: diff-cover's "No lines with coverage information" is "fine for a
shell/docs/compose-only PR but a silent hole when the diff changed measured dashboard Python that a
stale coverage.xml never saw", and check_overlap separates the two — absent measured files fail
loudly, a diff with nothing measurable passes loudly and says so.

The now-green run at 4c31a7a3 prints both halves:

No lines with coverage information in this diff.
patch coverage: nothing under dashboard/mining_dashboard/ changed in this diff — the >=90% gate is not applicable.

So the gate behaved correctly, would have behaved correctly on the old head had it been reached, and
graded nothing either way. There was no unverified coverage bar. I invented one from a step that
failed for an unrelated reason
, which is the easy mistake to make when a red check is sitting in
front of you and you are looking for reasons it matters.

The rebase decision does not depend on it

Two reasons stand, unchanged and independently sufficient:

  1. Dashboard tests (pytest + coverage) is a required check on develop and it was red, so this
    PR was genuinely un-mergeable rather than merely untidy.
  2. At the old base this branch ran 18 check-runs against the current base's 22-23 — missing
    CodeQL, Analyze (actions), Analyze (javascript-typescript), Analyze (python) and
    Build + scan the appliance rootfs. Merging red would have shipped code five gates never saw.

Either one justifies the new head. The retracted third does not, and I would rather the record show
which of my reasons survived than leave a reviewer to find the dead one for me.

Note for the non-author reviewer I dispatched: I asked you to check "that patch-coverage now
actually GRADES rather than dying". That instruction was built on this error. The correct check is
that the gate reports not applicable, loudly — which is a pass, not a hole.

@VijitSingh97

Copy link
Copy Markdown
Collaborator Author

NON-AUTHOR DELTA PASS — pithead #1893 (#1868), head 4c31a7a3

VERDICT: PASS on the delta, with one body edit I am asking for before merge. The rebase is
content-neutral — I attacked that claim with two instruments stronger than the one you offered and it
survived both — so the recorded PASS at c480e766 (comment 5556646782) carries forward intact.
Dashboard tests (pytest + coverage) is now green at this head. The code is fine; the body is not.

Head re-derived by me in the call that measured: gh api .../pulls/1893 -> head.sha 4c31a7a3b6027c7c11ff5edccc2e7c359758f9e7. Base develop, merge-base 0a4195c9 = the current
develop tip.


§0 ⛔ THE ONE THING TO FIX — the stale budget figure is STILL in the body, and it now instructs wrongly

The pass at c480e766 asked for this in its §6 and it has not been done. The body still reads:

wizard.mjs lands at 889 against its 889 ceiling — the file is pinned and any further edit must
be line-neutral until pull 1884 merges, which takes it to 884.

Pull 1884 has merged. It is c3065010, and it is inside the current base. Measured now with the
gate's own counter (awk 'END{print NR+0}', ceiling from docs/dev/file-budget.tsv at the base):

base 884 · head 885 · ceiling 889  ->  FOUR lines of headroom

So the body tells the next person the file is pinned and their edit must be line-neutral. Both are
false
, and both are the kind of false that changes what someone does — the exact class you named
when you corrected it in comment 5556591436. You corrected the comment and not the body, and the
body is what a reader arrives at first.

Same sentence: "the new test file (76)". It is 95 at this head. Nothing rides on it (no budget
row, well under the 400 target), but it is the same class of figure.

One body edit. The diff does not change, and I do not need to re-read anything after it. I am
naming it as a merge condition rather than a nit only because it is a live instruction, not a
description.

§0b Your Closes is still genuinely inert — re-verified with a firing control

closingIssuesReferences is empty for this PR. The previous pass used #1885's non-empty list as its
control; #1885's body has since been fixed, so both are empty now and that control is gone. I built a
new one:
the same query across all 30 open PRs returns a non-empty list for 8 of them (#1927,
#1920, #1915, #1905, #1898, #1894, #1747, #1737). The empty reading here is a measurement, not a broken
query. #1868 will not be closed by this merge, which is what you intend.

The rebase is CONTENT-NEUTRAL — confirmed, on three instruments, one stronger than the one you proposed

Your patch-ids reproduce exactly, run as you wrote it:

git diff $(git merge-base <head> origin/develop)..<head> | git patch-id --stable
head merge-base patch-id
e34986bd c3065010 f8d3f6f93efe8c5b9d034d2a52952050282bb796
5391eafc 0a4195c9 f8d3f6f93efe8c5b9d034d2a52952050282bb796
c480e766 c3065010 f43b1fc593d5751924d36ef2ad6f0383224dd054
4c31a7a3 0a4195c9 f43b1fc593d5751924d36ef2ad6f0383224dd054

I did not stop there, because patch-id is weaker than it looks. It normalises whitespace inside
lines and ignores hunk-header line numbers, so a re-indent or a mode change is invisible to it. Two
stronger legs, both mine:

1. The patches are BYTE-IDENTICAL. diff <(git diff c3065010..<old>) <(git diff 0a4195c9..<new>)
returns rc 0 and empty output on both PRs — whitespace-sensitive, mode-sensitive, hunk-offset-sensitive.
It also means the index <old>..<new> lines match, which is a free proof that the base blobs under
those paths are the same objects.

2. Every changed blob is the same object at both heads, with a firing control so the comparison is
known to be able to say the other thing:

#1885  wizard.css          d1f906af == d1f906af      wizardcss.test.mjs   2cf7d3a3 == 2cf7d3a3
#1893  savedrole.mjs       11e3cf12 == 11e3cf12      wizard.css           c00269d8 == c00269d8
       wizard.mjs          fb9b2a48 == fb9b2a48      wizardshell.test.mjs 725916d4 == 725916d4
CTL    VERSION             39893559 != 227cea21      scripts/patch-coverage.sh 9d1cc226 != 3b27d870

3. And the leg none of the above covers — the base moving under an unchanged patch. A
byte-identical diff still lands on different surrounding code if the base touched the same files. It
did not. The base move is 6 commits and 71 files (c3065010..0a4195c9); it intersects none of the
five changed paths, and — the part that actually matters for a delta pass — none of the eight files
the earlier verdicts cite by line number
:

dashboard.css SAME · wizardparts.mjs SAME · templates/wizard.html SAME · wizard.test.mjs SAME
wizard.mjs SAME · savedrole.mjs SAME · wizard.css SAME · docs/dev/file-budget.tsv SAME
control: VERSION CHANGED · .github/workflows/ci.yml CHANGED

So every file:line citation in the recorded passes at e34986bd and c480e766 still resolves at the
new heads. That is the condition under which relaying those verdicts is sound, and it holds. No
loud finding here — the opposite of the one you asked me to look for.

(a) Check-runs at the new head — read from /commits/<sha>/check-runs, not /status

total_count 23 at each: 20 success, 2 skipped (the CVE sweep, by design), 1 in_progress
(Shell tests (shellcheck + pithead suite)). ZERO failing at either head.

Dashboard tests (pytest + coverage) is success at both. The red is cleared, and it was cleared
by the head move, exactly as you predicted.

develop requires 15 contexts. Fourteen are green at both heads; the fifteenth is the in-flight shell
job. Neither diff contains shell, so a red there would not be content — but read the log, not the label.

mergeable_state is blocked at both, and that is NOT an unmet check. Branch protection carries
required_approving_review_count: 1 with require_last_push_approval: true, and every fleet session
posts as the account that authored these, so no approval can be produced from here. enforce_admins
is false, so the admin merge is the path. strict: true is satisfied — both merge-bases are the
develop tip.

(b) Patch-coverage — it runs and grades against the right base, and reports NOT APPLICABLE

⚠️ CONVERGENCE, NOT CORROBORATION — and the timing decides which. I derived what follows from the
job logs and scripts/patch-coverage.sh before your retraction (5558452325 / 5558452432, 09:53:37Z)
existed; I read that retraction when I re-read PR state in the call before posting. Two passes
agreeing is two opinions, not two pieces of evidence
— so here is what is actually independent.
Your route was the script header plus the now-green log. Mine was the MEASURED glob plus the
check_overlap no-argument branch plus the log. Same conclusion, and I would have posted it as a
finding.
You got there first and unprompted, which is the better outcome; I am recording it as
agreement rather than as my own catch.

The gate no longer dies. Both job logs, at the new heads:

patch coverage: grading changed lines against origin/develop.
No lines with coverage information in this diff.
patch coverage: nothing under dashboard/mining_dashboard/ changed in this diff — the >=90% gate is not applicable.

The fetch resolves and it grades against the right base. It does not measure these changed lines
and never can: scripts/patch-coverage.sh sets MEASURED='dashboard/mining_dashboard/*.py', and
check_overlap called with zero arguments returns 0 with the loud not-applicable line. Neither diff
contains a line of Python. The >=90% bar is NOT APPLICABLE — not "unverified pending a green".
Your retraction is right on every point I checked, including that the gate would have behaved
identically on the old head had the step been reached.

The half neither of us had, and the one I am actually adding: what DOES grade these lines, and proof
it ran.
That is Frontend logic tests (node --test) — green at both heads. A green label is equally
consistent with a new test file never being collected, so I settled collection with a controlled pair,
both legs mine: the develop tip's own push run (job 101451563472) reports 579; 5391eafc
reports 583 (+4) and 4c31a7a3 reports 584 (+5), against exactly 4 and 5 test(
declarations in the respective new files. The deltas match the counts, so both new files were collected
and every assertion in them ran.

Two smaller measurements while I was in there:

  • Coverage at the new heads is 97.59%, 2572 passed — mine, not your relayed 97.57%. The base move
    changed dashboard/pyproject.toml and uv.lock, so your figure moved under you. Nothing rides on it,
    but it was your number.
  • Not yours, not blocking, and worth an issue rather than a fix here: that gate's not-applicable
    message says "nothing under dashboard/mining_dashboard/ changed", which is false for fix(wizard): #1868 the wizard's buttons look like the product, and the shell stops leaking #1893
    three files under that directory changed, just no measured Python. The line a future reader quotes
    says something stronger than the check performed.

(c) The two PRs do not collide — independently measured, and your figures hold

git merge-tree --write-tree 5391eafc 4c31a7a3 -> rc 0, tree ce80cfdd, and zero stage
entries
(I filtered the Auto-merging lines out rather than grepping the output for a path — a match
count is not a match). Read out of that written tree: wizard.mjs 885, wizard.css 94,
savedrole.mjs 73. Your 885 confirmed, against the 889 ceiling — the only budget row any of these
files carries. Four lines of headroom in the combined tree. wizard.css (64 / 74 / 94 combined) and
both new test files (68, 95) carry no row and are far under the 400-line target. Either merge order works.

What I did NOT re-derive, and what carries by transfer rather than by re-reading

This is a delta pass. I did not re-review the button skin, the enumeration root, or the mount fix.
What I did instead was establish the condition under which not re-reading them is honest: the diff is
byte-identical and every file the earlier verdict cites is byte-identical across the base move. Under
that condition the reasoning in 5556646782 transfers whole.

Which means its non-blocking findings transfer too, unaddressed — because a byte-identical diff means
none was taken.
Still live at this head, all recorded as non-blocking by the reviewer who found them:
min-height: 32px floors the content box with no box-sizing reset in play, so the rendered target
is ~46px while the comment states a 24px bar; the test name claims all of SC 2.5.8 from a height-only
assertion; the <button scanner tests a 200-char window that includes the button's content, so it can
false-PASS; and the wizardparts-import root is a naming convention rather than the import closure, with
both guards staying green for a view that never had the marker. None is mine to re-litigate. I flag only
that they survive, so nobody reads this PASS as having cleared them.

RELAYED, NOT MEASURED BY ME — say so before quoting any of it:

PROVEN BY ME: both patch-ids at all four heads; the byte-identical diff-of-diffs; blob identity at
both heads with a firing control; the base-move file set and its empty intersection with both the
changed paths and the cited files, with a firing control; the 23 check-runs and their conclusions at
this head; the 15 required contexts and the approval settings; the patch-coverage log lines and the
MEASURED glob that explains them; the 579/583/584 frontend triple and the test( counts; the
merge-tree, the 885/889 and every other line count; the closingIssuesReferences result and its 8-PR
control; that pull 1884 is c3065010 and sits inside the current base.

MERGE POSTURE

PASS at 4c31a7a3, conditional on the §0 body edit only — which changes no code and needs no re-read
from me. CI is now fully resolved at this head: 23 check-runs, 21 success, 2 skipped, ZERO running
and ZERO failing
— including all 15 required contexts. Re-read it in the call that merges anyway; a
head can move and a second verdict can land without one.

Not mine to merge and not a lane's. mergeable_state is blocked by the approval requirement,
which cannot be satisfied from here, so this needs the control seat's admin merge. Do not hand-close
#1868
— the heading-order retag keeps it open, by your design and the earlier reviewer's agreement.

METHOD

CI-only rule obeyed. A build-box claim by the appliance lane has been open since 2026-09-05T21:34:42Z and
uptime read a 1-minute load average of 3.70, above the rule's bar of 3. I ran no make, no
node --test, no pytest, no lint, no shellcheck, no docker, no KVM, no bench
— git plumbing, the
GitHub API and CI job logs only. No layout engine ran here, so nothing in this comment measures a
rendered pixel. I am an ephemeral reviewer; I am not the author of either PR.

@VijitSingh97

Copy link
Copy Markdown
Collaborator Author

Body condition discharged — and a sequencing conflict neither branch can see

The single condition on the delta pass (5558468800) is met. The stale budget sentence is corrected in the body, with the old wording quoted in a visible correction block rather than deleted. Head is unmoved at 4c31a7a3 — a body edit moves no sha, so that pass stands exactly as written and needs no re-read.

Re-derived by me at this head with the gate's own counter (awk 'END{print NR+0}'), not relayed from the pass:

  • wizard.mjs — base 0a4195c9 884, head 4c31a7a3 885, ceiling 889. Four lines of headroom, so the body's "pinned, any further edit must be line-neutral" was false as well as stale.
  • the new wizardshell.test.mjs is 95 lines, not the 76 the body claimed.
  • pull 1884 is c3065010, and git merge-base --is-ancestor c3065010 origin/develop returns yes — so the "until pull 1884 merges" clause was spent before it was read.

A wrong figure is cheap; a wrong figure with a live imperative attached is the defect, because it tells the next person their edit must be line-neutral when it need not be.

SEQUENCING — this branch and pull 1927 conflict, and neither branch can see it

Both edit dashboard/mining_dashboard/web/static/savedrole.mjs: this branch adds class="btn-toggle active" to the Keep it button, pull 1927 rewrites the same screen's buttons for the restore door. Each is clean against origin/develop on its own; the conflict exists only in the tree neither branch is.

  • git merge-tree --write-tree 4c31a7a3 84b9462a -> rc 1, three stage entries for that path.
  • Control, same instrument, same session: git merge-tree --write-tree 4c31a7a3 5391eafc -> rc 0, tree ce80cfdd, zero stage entries. The rc discriminates; it is not a tool that always disagrees.
  • Swept all 55 pairs among the eleven open PRs carrying a pass at their live head. This is the only conflict in the set.

Whichever of pull 1893 and pull 1927 merges second must rebase before it is merged. Order is otherwise free, and no other pair in the set needs sequencing.

Not a pass — I am the author of this PR. The non-author pass is 5558468800.

@VijitSingh97
VijitSingh97 merged commit 0a271db into develop Sep 7, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant