Settings panel — premium macOS/iOS restyle

Phase G is a bench-only visual round on /home/loca/dev/wrdp: the QuizWizz Settings panel restyled to one Apple inset-grouped visual language, driven by a capped vision loop and closed by a keyboard walk and the standing gates. It changes CSS and class attributes only, closes no product decision and authorises nothing. Every patch on this page is releaseEligible: false; the release owner carries source through the locked pipeline.

1. Scope and identity

  • bench and boundary:
    • bench /home/loca/dev/wrdp, one page: /wp-admin/admin.php?page=qw-settings
    • viewports 1280x900 desktop and 390x900 mobile, WordPress fresh colour scheme
    • plugin mirror HEAD 715474f, unchanged by this round — nothing was staged or committed
    • ./scripts/qwgit.sh status --porcelain counts 51 M rows and 0 ?? rows, against 49 M after round F
  • identity, per the bench law that owner-identity Settings writes destroy operator history:
    • every capture, every walk and every gate ran as the disposable administrator qwvisual-20260914 (ID 1171); never user 1
    • the admin’s admin_color user meta was switched from modern to fresh before round 0, so every round is scored against the same core chrome. That is WordPress user meta, not a QuizWizz setting, and it is the only deliberate state change of the round
    • zero QuizWizz Settings saves happened in phase G. The option SHA-256 stayed 89877cf601ca195bf0ff75c936637e485d8b6de454c301934bff54ab0838ff9f and wp_qwizz_settings_backups read 94 rows with 10 owned by user 1 after every round, every gate and the deletion below
    • the disposable admin was deleted after the gates with wp user delete 1171 --yes, against 0 backup rows and 0 posts owned by 1171 at that moment. Administrators are now admin alone. Round F had deliberately retained this identity; phase G used it and gave it back
    • re-read while writing this receipt: option SHA 89877cf6…, total=94 owner1=10, one administrator (1:admin), user 1171 absent
  • evidence locations:
    • private, mode 0700: evidence/2026-09-14-uiux-round/settings/ — 130 files (129 plus the self-excluded inventory row), allJsonParse true, 48 board PNG/JSON pairs of which 42 are scored and 6 are the final unscored board, 11 auxiliary PNGs, 7 critique records covering 56 vision evaluations, 6 fix lists, the preserved failed and artifact keyboard walks and the rejected experiment’s walk (file-inventory.json)
    • public-safe copies: board-settings.png, settings-scores.md, settings-keyboard-walk.md

2. Specification as shipped

The binding contract was the phase G design specification: system type stack with helper text never below 12px, iOS/macOS inset-grouped lists with one containment edge per section and a 16px-inset hairline between rows, boolean settings as 40x24 desktop / 51x31 mobile switches whose knob shadow is the one permitted control shadow, segmented controls for the tab and tier choosers, a glass toolbar that keeps its 52px geometry and the superglued-rail stacking, a --qw-canvas page field, motion limited to background-color, border-color, box-shadow, opacity, transform at 150–200ms with a reduced-motion opt-out, and the geometry contract of the design board at :163-198. The three new admin tokens are verbatim, at wp/wp-content/plugins/quizwizz/assets/css/qwizz-tokens.css:91-93:

--qw-radius-group: 12px;
--qw-radius-control: 8px;
--qw-canvas: color-mix(in srgb, var(--qw-line2) 40%, white);

The single accent is the second qwizz-tokens.css change, and it is scoped rather than global: .qwizz-settings.qwizz-settings--premium at :398-406 sets --qw-brand: #6d28d9, --qw-brand-dark: #5b21b6, --qw-focus: var(--qw-brand) and a --qw-ring mixed from the same hue at 28%. Every accented state in the panel resolves through that one family — the switch ON track at qwizz-admin-settings-core.css:2429-2430, the selection dots at :2584-2585 and :2651-2652, and accent-color at :2918 — while every other QuizWizz admin surface keeps the WordPress-theme-derived tokens of the shared block at qwizz-tokens.css:39-50, whose ten-selector list includes .qwizz-settings itself and is beaten only on the premium root by specificity. The keyboard walk measured the resulting focus outline as rgb(109, 40, 217), which is that hex.

Six owned files. The SHA-256 values below are the live bytes, re-hashed for this receipt and equal to the staged record /tmp/qwfix/settings/round-7-focus/sha256.txt:

File under wp/wp-content/plugins/quizwizz/Live SHA-256Rounds that changed it
assets/css/qwizz-tokens.cssf30aefd31a3684f3ae61200e569e3402e991103a74be5428844f42823a0b854b1, 2, 3
assets/css/qwizz-admin-settings-core.css4e93db131f0b57ca7f143ae9ca1a2e4a917f47a67129606950a4e7959daa3d591, 2, 3, focus amendment
assets/css/qwizz-admin-settings-layout.css109869be46bf725986259fca3928cee6488dd98494f7f8fcf8d0cb762442071a1, 2, 3
assets/css/qwizz-admin-settings-search-preview.css04b7b6a69a78611e101d21dc1306221f17131e6bdeed8d4fbc2cf781bac29cf21, 2
assets/css/qwizz-admin-settings-workspace.css74ea11c77c426a62c8764d1c0e7dcdd41e49f05bf806875309859a1899979a8d1 through 6, focus amendment
includes/Admin/views/settings-page.phpc7131d8d02a9814b9d5bdc1f0636f9fee5620aaa6167aee07e24a0ad9f2bb0c31 only

Each install went through the bench protocol — stage under /tmp/qwfix/settings/round-<n>/, install -o www-data -g www-data -m 0644, then php -l in the container for the view. No file under vendor/, build/, provenance/ or QUIZWIZZ_VERSION was touched.

The PHP view carries class additions only, and that is measured rather than asserted: stripping every class="…" attribute from the round-F staged pre-G copy (/tmp/qwfix/uiux/save-status/includes/Admin/views/settings-page.php) and from the live file leaves zero differing lines. 85 lines gained class tokens and no line lost one, introducing 12 new tokens — qwizz-settings--premium, qwizz-settings__canvas, qwizz-settings__toolbar--glass, qwizz-settings-cluster, qwizz-settings-group, qwizz-settings-group__body, qwizz-settings-group__eyebrow, qwizz-settings-row, qwizz-admin-switch--ios, qwizz-search--pill, qwizz-segment--control and qwizz-segmented-tabs. The view reached its final bytes in round 1 and no later round changed it.

JavaScript is untouched, also measured: live assets/js/qwizz-admin-settings.js is SHA-256 14cea53d553d3498cb4a7fda8bf34aadb56ed76bba69c4851baa90adc5ea1b84, byte-identical to the round-F staged copy of the same file.

One correction to the round’s own summary: the four Settings sheets are token-driven, but they are not hex-free. qwizz-admin-settings-workspace.css:995-996 uses #000 and rgba(0, 0, 0, .35) as the stops of the segmented strip’s right-edge fade mask-image. Those are mask alpha stops rather than paint colours, and no other hex literal appears in the four sheets.

Shipped state is vision round 6 plus the CSS-only round-7-focus amendment, which was authorised after the six-round cap to repair focus-ring clipping. The amendment was not vision-scored, so it does not contribute to any score in section 3.

Two changes the specification forbids were recorded instead of made, because the plan freezes JavaScript:

  • search-result rows concatenate a setting’s label and its value into one string — rendered as Page numbersDefault, Name / Date / Class linesDefault and Pre-print fine-tune editorDefault. The cause is searchIndexCopy at assets/js/qwizz-admin-settings.js:1012-1018, which clones the row, removes effect sentences, screen-reader text, help and segments, and returns the remaining textContent with no separator; the result is consumed for the rendered row at :1434. This was the critic’s top residual from round 4 onward.
  • the mobile segmented strip does not scroll a focused off-screen segment into view. A fix needs a focus handler calling scrollIntoView, and the file contains no scrollIntoView, no focusin and no focus listener at all.

Both are carried as ledger rows in section 9.

3. The vision loop

Six PNGs per round — top, group and search at each of the two viewports — were evaluated with the P-settings prompt verbatim from /tmp/qwfix/uiux/prompts.txt, and the two top-of-page PNGs additionally with P-first5, for 8 evaluations per round and 56 in total across 7 critique records. The seven axes the prompt scores 1–10 are hierarchy, density and rhythm, alignment and spacing consistency, contrast and legibility, apple_feel (inset grouped lists, hairline separators, rounded controls, restrained single accent, system type), discoverability of actions and polish; the min axis column below is the lowest of those seven across all six PNGs of the round.

roundmin axisP1P2P3
0 (baseline, pre-restyle)309626
1308338
2308233
3308136
4307735
5407433
6408637

The round did not converge. Convergence required two consecutive rounds in which every axis on every PNG scored at least 9 with zero P1 and zero P2 findings; verdict.json records qualifiedRounds: [], so the six-round cap decided the outcome instead. The plan’s fallback rule is the highest minimum axis score with ties going to the latest round: rounds 5 and 6 tie at minimum axis 4, so round 6 shipped. Per-round tables are in settings-scores.md; the raw records are critique-round-<n>.json, the fix inputs fixes-round-1..6.json, and the decision verdict.json.

What actually moved the design between rounds was the orchestrator’s own screenshot readback, not the critic. Each item below was observed in the round’s captures and fixed in the next:

roundreadbackwhere it went
1boolean settings still painted as WordPress-blue native circles inside nested bordered sub-fieldsets, and the stepper was clippedswitches, single containment edge and stepper repair in round 2
2the shared dashbar rail lost its secondary stat links, caused by a Settings-page stat overrideoverride reverted in round 3, rail restored
3on mobile with search expanded, toolbar icons and the Saved pill overlappedround 4 made the segmented strip scrollable and moved the actions cluster trailing
4on desktop with search expanded, the nine-tab strip overlapped its own labelsround 5 gave segments flex-shrink: 0 inside an overflow-x: auto strip; round 6 added the right-edge fade mask at qwizz-admin-settings-workspace.css:995-996
6clean at both viewports in all three statesshipped

Every score, severity and axis in this section is a model judgement over a static screenshot. They are [INFERENCE], never measurements, and no ledger row moves on them. Two further caveats belong to the record: the desktop top, group and search PNGs are byte-identical between rounds 3 and 4, because round 4 was a mobile-only change, yet the critic scored them differently — 42 scored PNGs carry 39 unique SHA-256 values (settings-scores.md). Score movement on identical bytes is evaluation variance and must not be attributed to CSS. And round 6’s P2 count rises to 86 from round 5’s 74 on a strictly later build, which is the same variance seen from the other side.

4. Keyboard walk

Run at both viewports as the disposable admin after the focus amendment, recorded in keyboard-walk-final.json and summarised in settings-keyboard-walk.md. Scope split: Settings means focus stops inside .qwizz-settings; core means every other stop on the page, including the WordPress admin bar, the admin menu and the shared stats rail. Core stops are out of scope for this round and were left unchanged.

scope and viewportstopsmissing indicatorsclipped rings
Settings, desktop363 → 08 raw → 0
Settings, mobile363 → 03 → 2
core, desktop680 → 011 → 11
core, mobile151 → 17 → 7

Desktop walked 104 stops in total and mobile 51.

  • one of the eight raw desktop clips was a collector artifact, not a defect. Step 92 is the opacity-0 checkbox fieldset#qw-tier-player-sound-default > label > input for player.sound_default: the collector measured its invisible 3px external outline while the visible inset-ring proxy was on screen. The other seven raw clips concern visible elements and are genuinely repaired. Do not present all eight as product defects.
  • the two remaining mobile clips are the Player and Advanced segments of the scrollable strip. Focused, their boxes reach 235.95px and 244.61px against the strip’s clip edge at 210px, so they overhang by 25.95px and 34.61px. The strip reports scrollLeft: 0, scrollWidth: 394, clientWidth: 169 and scrollBehavior: auto both 80ms after focus and again after the 600ms settle, so the user agent does not scroll a focused segment into view and this is not a smooth-scroll timing artifact (keyboard-final-artifact-check.json). Closing it needs JavaScript, which phase G froze — ledger row in section 9.
  • the Preview modal passed on both viewports: it opens, focus lands inside it, Escape closes it, and focus returns to the exact opener.
  • search behaves, but the interim assertion did not. Typing pdf reports 5 matches, and the visible indexed setting rows outside the results panel move 7 → 85 of 87 indexed candidates at both viewports. Search reveals matching rows across tiers rather than hiding non-matches, so the plan’s “filters” wording is satisfied by the results panel while the orchestrator’s interim “row count decreases” assertion was mis-specified. It is recorded as a mis-specified assertion, not as a product defect, and the walk’s own overall verdict therefore reads FAIL.
  • console errors 0 and page errors 0 in every walk, before and after the amendment.

5. Functional gates

Browser smoke, harness scripts/quizwizz-browser-smoke.js at SHA-256 6d0803b2121ef1d352e5a1b6d606aae915773902257214cc118f303ac7b25289, one bounded chunk at a time on an idle bench with a single harness owner, identity resolved through profile.php only:

ChunkResultRecord
legacy47/47g-legacy.json
uiux-perfection with QWIZZ_UIUX_STEPS=a-d7/7g-uiux-a-d.json
uiux-perfection with QWIZZ_UIUX_STEPS=h-j7/7g-uiux-h-j.json
tier-selector,single-column,selection-borders,superglued-rail,welcome10/10g-admin-settings.json

Every record reports overallPass true, consoleErrors and pageErrors empty, restoration.complete true, foreignRowsDuringRun=[], and both identityEqual and survivingIdentityEqual true. The harness created writers 1198, 1199, 1200 and 1201, one per chunk, and deleted each; the legacy and admin-selector chunks each removed one auto-draft, the two uiux-perfection chunks none. Records are private under evidence/2026-09-14-uiux-round/smoke/.

Shell harness scripts/quizwizz-probe.sh at SHA-256 eacd72ceef6a1f86…, --wave all on the idle bench: exit 0, TAP 1..98, # assertions=98 failures=0, zero not ok and zero Bail out! lines. Wave 9 printed # admin_tree 2q build 10q attempt_payload 5q settings page 4q / 150490 bytes, inside its 6 / 10 / 8 / 13-query ceilings. logs/debug.log measured 38,699 lines before and 38,699 after. The TAP record is /tmp/qwfix/settings/wave-all-g.tap, SHA-256 b653db020e0787bb95d5a81690a4d47ed41692637d339ec77ee00cb75c9c1d22.

All gates ran against the exact shipped bytes: the focus amendment was installed before them and the scroll-snap candidate of section 6 was installed and reverted afterwards.

Two coverage caveats, because a green chunk is not proof of a contract nobody asserts:

  • the a-d step group emits exactly three UI/UX assertions — UIUX (a) scroll-spy ownership, UIUX (b) assistive-only effect sentences and UIUX (c) the icon/text gap with its checked >= 60 denominator. There is no UIUX (d) assertion in the harness. The Preview modal appears only as two waitFor barriers at scripts/quizwizz-browser-smoke.js:3173-3179, which open the modal and close it through button[data-preview-close]; nothing there is a counted assertion, and nothing there checks focus containment, Escape or focus return. The Preview contract is covered by the keyboard walk of section 4 alone.
  • the probe harness page and the harness skill still describe the shell plan as 1..76 in places, while the installed harness emits 1..98. That is documentation drift against installed bytes; it is recorded as a ledger row rather than repaired by editing counts this round did not measure.

6. The rejected scroll-snap candidate

One further attempt was made on the residual mobile clip and it failed, so it is recorded rather than hidden. A round-8 CSS-only candidate added exactly two rules to qwizz-admin-settings-workspace.css: scroll-snap-type: none on .qwizz-workspace-nav, .qwizz-anchor-strip and .qwizz-segmented-tabs under :focus-within, and scroll-snap-align: none on their focused anchors. The staged candidate is /tmp/qwfix/settings/round-8-focus-snap-candidate/, workspace sheet SHA-256 5617a492daebfff0baa76083e4c7bdd09345595a03db7ec7f62ad6ec2691648f.

It was installed at 06:11:51Z, measured ineffective and reverted at 06:14:48Z; the staged record carries its own revert annotation at 06:15:05Z. The measurement is keyboard-walk-snap.json: the Player overhang was unchanged at 25.95px and the Advanced segment measured 280.61px against the same 210px clip edge, a 70.6px overhang, with scrollLeft still 0. That 70.6px figure belongs to the rejected experiment only; the shipped residual is 25.95px and 34.61px as recorded in section 4.

Live qwizz-admin-settings-workspace.css is back at the round-7-focus bytes 74ea11c7…, which is the value in the section 2 table.

7. Board and public evidence

One board, 2400x1120, SHA-256 efa9c79e7ed741d29e7b78d318240f1cf2d289d455395dec319e55d41e6a1d02: two rows of six tiles, round 0 against the final build, in the order top, group, search at desktop then the same three at mobile. Every tile was inspected before publication and none shows a credential, address or private identifier, so no redaction was applied.

Settings panel round 0 vs final

The two published companions are settings-scores.md, 219 lines carrying the per-round axis tables, the final keyboard table and the bench-invariant lines, and settings-keyboard-walk.md, 66 lines carrying the walk as recorded, including its overall_pass: false.

8. Mirror census after G

Phase G changed six files, two of which were not tracked-modified before, so the mirror census moves from 49 rows to 51 at the same head 715474f, with 0 ??. The refresh artifact is evidence/2026-09-14-mirror-decision/census-refresh-g.json, created 2026-09-14T06:15:40Z, and it carries the same two limits as the round-F refresh: it bypassed the original 41-row assertion, and it did not re-run the canonical apply-check.

ClassRows after round FRows after phase G
post-sep8-unattributed1316
post-packet-mutation89
packet-clean-base66
packet-on-drifted-base54
inherited-unpacketed32
inherited-catalog-artifact1414
total4951

Four movements account for the whole delta:

  • assets/css/qwizz-admin-settings-layout.css moved packet-on-drifted-basepost-packet-mutation, because its live bytes no longer equal the sealed Sep-8 afterSha256.
  • assets/css/qwizz-admin-settings-workspace.css moved inherited-unpacketedpost-sep8-unattributed.
  • assets/css/qwizz-admin-settings-search-preview.css is a new row, post-sep8-unattributed.
  • assets/css/qwizz-tokens.css is a new row, post-sep8-unattributed.

The other two owned files were already carried by the round-F refresh: qwizz-admin-settings-core.css as post-packet-mutation and includes/Admin/views/settings-page.php as post-sep8-unattributed. Per-file detail and the owner’s options per class are on the mirror decision sheet. Phase G makes the release owner’s decision harder in the same direction round F did: nine packet paths now carry bytes the sealed packet does not describe, and one more Settings sheet has left the inherited classes.

9. What remains

Eight new ledger ids, all recorded in the open-work ledger so none can be quietly lost. One of them, UIUX-SET-02, no longer remains: the operator-approved JavaScript focus fix recorded in section 11 closed it after this receipt was first written, and it stays in the table with its closing evidence rather than being deleted.

idresidualclass
UIUX-SET-01search-result rows concatenate label and value (Page numbersDefault). Needs a JavaScript change, and the phase G freeze was lifted only for the section 11 focus fix, so this one is still recorded rather than made.bench-actionable
UIUX-SET-02closed 2026-09-14 by the approved focus listener of section 11. The mobile strip now scrolls a focused off-screen segment into view and both viewports measure 0 clipped Settings-owned rings.shipped
UIUX-SET-03partially visible scroll-strip tabs on mobile read as truncated labels rather than as an affordance.owner-decidable
UIUX-SET-04helper, eyebrow and secondary-text density at both viewports.owner-decidable
UIUX-SET-05section-header insets and supporting-text alignment differ across Available experiences, Quiz size and Content languages.owner-decidable
UIUX-SET-06the core Screen Options control sits outside the plugin’s content frame on mobile and precedes the settings title.owner-decidable
UIUX-SET-07the harness has no UIUX (d) assertion and no counted Preview-modal assertion.bench-actionable
UIUX-SET-08documentation drift: 1..76 still appears where the installed harness emits 1..98.bench-actionable

UIUX-SET-03 through UIUX-SET-06 are round-6 critic judgements over static screenshots, so they are [INFERENCE] and owner-decidable by construction; no bench measurement moves them. Two of them were summarised more sharply than the record supports, and the difference is stated on the ledger rows: round 6 records a header-inset and supporting-text-alignment inconsistency rather than a missing helper line under Quiz size, and it records Screen Options as consuming height and following its own alignment rather than floating over the mobile rail. Nothing in critique-round-6.json or the final measurement JSONs establishes an overlap with the rail.

Debts carried from earlier rounds are unchanged by phase G: UIUX-P1 and UIUX-VIS-01 through UIUX-VIS-06 from round F, UIUX-BACKUP-01 for owner 1’s 11 backup rows against a per-writer cap of 10, and the mirror decision itself.

10. Explicit non-claims

  • not converged. The loop hit its six-round cap with qualifiedRounds: []; round 6 shipped on a tie-break, and the focus amendment was never vision-scored.
  • no human acceptance. Nobody has reviewed or accepted this surface. This receipt records measurements, gates and model judgements only.
  • no release eligibility. releaseEligible: false. Nothing was staged, committed or pushed; mirror HEAD is still 715474f.
  • every axis score, severity and finding in section 3 is a model judgement over a static screenshot. They are [INFERENCE], and the round-3/round-4 identical-byte scoring shows the variance directly.
  • the keyboard walk’s overall verdict is FAIL, and this page does not present it as a pass. The core failures are out of scope and unchanged, and the mis-specified row-decrease assertion is recorded as mis-specified. Section 4’s two remaining mobile Settings clips are the shipped-round reading; they are closed by section 11, whose own walk still records the same visibleRowsDecreased failure and therefore the same FAIL verdict.
  • the browser chunks cover no Preview-modal assertion and no UIUX (d); that contract rests on the keyboard walk alone.
  • the mirror apply-check was not re-run against the phase G tree, so the exit-1 seven-path result on the decision sheet still describes the pre-round-F packet.
  • no production wiki build or deployment was run by this writer, and no page from this round is claimed live.

11. Amendment — approved JavaScript focus fix (2026-09-14)

After the keyboard gate of section 4 was reported blocked on the mobile strip residual, the operator explicitly approved the smallest bounded JavaScript correction in assets/js/qwizz-admin-settings.js. The phase G freeze on JavaScript was lifted for that one change only: UIUX-SET-01 was not touched and stays recorded rather than made. Sections 1 through 10 remain the round’s own record; where they measure the pre-amendment bytes they are superseded here rather than rewritten, including section 2’s live JavaScript reading 14cea53d… and section 4’s two remaining mobile clips.

The change is one focus listener per [data-workspace-tab], inserted immediately before the existing click listener inside the workspaceTabs.forEach at wp/wp-content/plugins/quizwizz/assets/js/qwizz-admin-settings.js:1479. The listener occupies :1485-1491: it returns unless the segment’s strip (tab.parentElement) is horizontally scrollable — strip.scrollWidth <= strip.clientWidth returns at :1487 — and unless the segment’s rect lies outside the strip’s rect, which returns at :1488-1489; otherwise it calls tab.scrollIntoView({ block: 'nearest', inline: 'start', behavior: 'auto' }) at :1490. The in-file rationale is at :1480-1484, the click listener still begins at :1492, and no other file changed.

The first attempt was measured ineffective and is recorded rather than hidden. Both attempts were installed from the same staging directory, /tmp/qwfix/settings/round-9-js/, with install -o www-data -g www-data -m 0644 after a clean node --check; the retained staged copy and its sha256.txt carry the final bundle only, at a7b6768e7fa555019936564552fc8bc9edd1d329bb3499f9248b935519ccbe77, which equals the live file. The live file reads www-data www-data 644.

attemptinline argumentlive SHA-256measured outcome on mobile
firstinline: 'nearest'1a3792befba2415e8ed0894b68173707915974af81e68bcfa631e58d9b043bd0ineffective. Player still clipped, 1 clipped Settings ring; the strip stayed at scrollLeft: 0 at the 80ms probe and at the settled measurement, segment 144.28–235.95 against strip 41–210 (keyboard-walk-js-1a3792be.json)
finalinline: 'start'a7b6768e7fa555019936564552fc8bc9edd1d329bb3499f9248b935519ccbe77effective at both viewports, 0 clipped Settings rings (keyboard-walk-js.json, confirmed by keyboard-walk-js-confirm.json)

nearest failed because the strip declares scroll-snap-type: x proximity at wp/wp-content/plugins/quizwizz/assets/css/qwizz-admin-settings-workspace.css:194: proximity snapping returns a small nearest scroll to the previous snap point, which is why the durable reading is scrollLeft: 0 at both probes. The probe-runner also observed the requested 26px scroll being pulled back to 0; that transient value is not preserved in an artifact, so the table above carries the measurement and not the transient. inline: 'start' targets the segment’s own snap point — every tab measures scroll-snap-align: start in keyboard-walk-js-confirm.json — so the snap holds the scroll instead of undoing it. The browser re-fetched the served bundle and matched the final SHA, which the walk records as expectedJsSha256; re-hashing the live file for this amendment returns the same value.

Keyboard walk on the final bundle, same collector and same scope split as section 4, run as the disposable admin at 390x900 and 1280x900 (keyboard-walk-js.json, captured 2026-09-14T11:27:28.964Z; mobile confirmation pass keyboard-walk-js-confirm.json at 11:44:12.998Z). Before is the shipped-round reading of section 4; after is this walk:

scope and viewportstopsmissing indicatorsclipped rings
Settings, mobile36 of 510 → 02 → 0
Settings, desktop36 of 1040 → 00 → 0
core, mobile151 → 17 → 7
core, desktop680 → 011 → 11
  • mobile geometry, measured per segment after the settle: Player at scrollLeft: 87 with the segment at 54.28–145.95 inside a strip of 38–207, Data at scrollLeft: 180 and Advanced at scrollLeft: 225 with 2.39px of clearance at the strip’s right edge. All four workspace tab stops report insideStrip true and clippedOutsideStrip is empty.
  • desktop is a deliberate no-op: the strip measures stripScrollWidth: 672 against stripClientWidth: 672, so it is not scrollable, the listener’s first guard returns and all four tab stops measure inside the strip unchanged.
  • core chrome is untouched by the change and unchanged in the measurement: 1 missing indicator and 7 clipped rings on mobile, 0 and 11 on desktop, exactly the section 4 readings.
  • the Preview modal passes at both viewports: it opens with focus inside on Close guest preview, Escape hides it, and focus returns to button.qwizz-preview-open. Search typed pdf reports 5 matches and passes. Console errors 0 and page errors 0 in every walk.
  • the walk’s own pass is still false, on the same single assertion as the shipped round: visibleRowsDecreased records before: 7, after: 85 at both viewports. That is the pre-existing artifact of the interim mis-specified assertion described in section 4, identical on the shipped round, and not a regression introduced here.
  • the plan’s line 210 acceptance — 100% visible focus indicators and 0 clipped rings across Settings-owned stops — is now met at both viewports. It was not met on the shipped round.
  • the failed first-bundle walks are preserved as keyboard-walk-js-1a3792be.json and keyboard-walk-js-1a3792be-confirm.json beside the passing pair, under the private corpus evidence/2026-09-14-uiux-round/settings/.

Gates re-run on the new bundle, one bounded chunk at a time on an idle bench with a single harness owner, at the unchanged harness bytes 6d0803b2…:

ChunkResultRecord
legacy47/47js-legacy.json
uiux-perfection with QWIZZ_UIUX_STEPS=a-d7/7js-uiux-a-d.json
uiux-perfection with QWIZZ_UIUX_STEPS=h-j7/7js-uiux-h-j.json
tier-selector,single-column,selection-borders,superglued-rail,welcome10/10js-admin-settings.json

Every record reports overallPass true, consoleErrors and pageErrors empty, restoration.complete true, and the backup-table identity digests equal on both readings — equal and survivingIdentityEqual true, with the harness rows it created tracked, requested and removed. Shell scripts/quizwizz-probe.sh --wave all on the same idle bench exited 0 at TAP 1..98 with # assertions=98 failures=0 (/tmp/qwfix/settings/wave-all-js.tap:288-289). logs/debug.log measured 38,703 lines before and 38,703 after; that is the current bench figure and supersedes nothing in section 5, which measured 38,699 at its own time.

Bench invariants after the amendment, re-verified read-only for this page: wp_qwizz_settings_backups holds 94 rows with 10 owned by user 1, qw_exposure_settings hashes to 89877cf601ca195bf0ff75c936637e485d8b6de454c301934bff54ab0838ff9f unchanged, admin is the only administrator and auto-drafts count 0. No QuizWizz Settings save was made by this amendment. The disposable administrator was re-created as ID 1208 for the walk and deleted afterwards against 0 backup rows and 0 posts; a single Quick Draft auto-draft (post 12640) created by a login redirect was attributed and removed, and user 1208 no longer resolves.

The mirror census was refreshed rather than assumed: evidence/2026-09-14-mirror-decision/census-refresh-js.json, created 2026-09-14T11:45:45+00:00 at head 715474f, reads 51 rows with the same class counts as the phase G refresh — 16 post-sep8-unattributed, 9 post-packet-mutation, 6 packet-clean-base, 4 packet-on-drifted-base, 2 inherited-unpacketed and 14 inherited-catalog-artifact. No class moved, because assets/js/qwizz-admin-settings.js was already a round-F post-packet-mutation row; the only row-level change is that row’s own current_sha256, from 14cea53d… to a7b6768e…, with numstat moving from 54 added / 43 deleted to 66 / 43 against the same packet_after_sha256 70a0a621…. Like the earlier refreshes it carries apply_check: null, so no canonical apply-check was run against this tree either.

Non-claims for this amendment, in addition to section 10: it was not vision-scored, no human has accepted the surface, the freeze lift covers this one change only, the walk’s overall verdict is still false on the mis-specified assertion, and nothing was staged, committed or pushed — mirror HEAD is still 715474f and the patch is releaseEligible: false. No production wiki build or deploy was run by this writer.