Skip to content

fix(a11y,design): modal aria-hiding root cause (#211) + first nitpick slice#212

Merged
HomenShum merged 6 commits into
mainfrom
qa/frontend-verify-nitpick
Jul 18, 2026
Merged

fix(a11y,design): modal aria-hiding root cause (#211) + first nitpick slice#212
HomenShum merged 6 commits into
mainfrom
qa/frontend-verify-nitpick

Conversation

@HomenShum

Copy link
Copy Markdown
Owner

Post-merge frontend verification + design-nitpick campaign, pass 1 of the loop.
Full evidence in .qa/evidence/20260717-postmerge/ (local, gitignored) and the QA
ledger (.qa/memory/).

Commit 1 — fix(a11y): aria-live pins defeated modal aria-hiding (closes #211)

Root cause differs from the issue's hypothesis: Radix's hideOthers worked, but the
aria-hidden package exempts every [aria-live] element AND its ancestor chain.
Explicit aria-live on the invite chip button, the 130-element chat feed, and the
status strip pinned large background subtrees screen-reader-reachable behind dialogs.

Fix: dedicated hidden LiveRegion leaf for the copy announcement; role="log" on the
chat feed (implicit polite, additions-only — better SR behavior); drop redundant
aria-live where role="status" exists. Also fixes the truncated composer
placeholder @nod@nodeagent.

Measured (aria snapshot lines, command palette open): 202 (pre-Radix) → 49
(post-Radix) → 20 (dialog-only, zero background leak). Guarded by new
e2e/modal-aria-hiding.spec.ts.

Commit 2 — fix(design): off-scale tokens + green-semantics violation

  • 11.2px11px (3 declarations, tertiary metadata)
  • .r-land2-trust: informational governance line rendered in success green →
    --text-secondary; 12.5px → 13px, 7px gap → 8px. Verified by computed style:
    rgb(198,204,212), no longer #2E9E6B.

Verification on this tree

  • Full e2e vs built preview: 90 passed, 0 failed (73 live-lane self-skips), 19.6m
  • Guard spec re-run post-rebuild: PASS
  • design:audit + ui:layer-audit: pass (589 files)
  • Typecheck: 0 errors · vitest full: 2,510/2,510 (pre-fix baseline; affected suites re-run green)
  • Regression sweep: 6/9 prior P0/P1s re-verified with today's artifacts; 3 mobile-deck
    items live only in the authenticated lane (skipped locally, unit analogs green)

Deliberately deferred (next loop passes)

Prettify audit scored V1/V2/V6 = 0 (13 font sizes, 73.7% off-grid spacing, 8 radii/
shadows/icon sizes; alwayson.css is its own drifted mini-system). That systematic
token migration needs its own judged, pixel-diffed pass per surface — not bundled here.
Open P2s in the QA ledger: mobile landing nav wrap at 375px, mobile metadata
truncation ("2…"), binder pinned/recent duplication.

🤖 Generated with Claude Code

HomenShum and others added 2 commits July 17, 2026 13:32
Root cause (differs from the issue's original hypothesis): Radix's hideOthers
was working, but the aria-hidden package exempts every [aria-live] element and
its ancestor chain. Three explicit aria-live attributes on real UI (invite
chip button, the whole 130-element chat feed, the status strip) pinned large
background subtrees screen-reader-reachable behind open dialogs.

- invite chip: announcement moves to a dedicated hidden LiveRegion leaf
- chat feed: role="log" (implicit polite liveness, additions-only announcements)
- status strip / offline pill: role="status" already implies liveness; drop
  the explicit attribute

Measured with the command palette open: aria snapshot 202 lines (pre-Radix)
-> 49 (post-Radix) -> 20 (dialog-only, zero background leak). Guarded by
e2e/modal-aria-hiding.spec.ts.

Also fixes truncated composer placeholder "@nod" -> "@NodeAgent" (Chat.tsx).

Closes #211

Co-Authored-By: Claude Fable 5 <[email protected]>
… violation

- styles.css: 11.2px (3 declarations, tertiary metadata text) -> 11px on-scale
- landing.css .r-land2-trust: informational governance line was rendered in
  success green (green is reserved for success semantics) -> text-secondary;
  12.5px -> 13px, gap 7px -> 8px (type/space scale)

Prettify audit context (.qa evidence, advisory): V1 type-scale 13 distinct
sizes, V2 off-grid 73.7%, V6 8 radii/8 shadows/8 icon sizes. The systematic
per-surface token migration (incl. alwayson.css) is deliberately NOT in this
commit — it needs its own judged, pixel-diffed pass.

Co-Authored-By: Claude Fable 5 <[email protected]>
@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
noderoom Ready Ready Preview, Comment Jul 17, 2026 11:57pm

Request Review

@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown

Scaffold Handoff — For Your Coding Agent

Your coding agent (Codex, Claude Code, etc.) should apply the accepted
scaffold proposals below. Do NOT touch any immutable files.

Immutability Check

Mode: advisory

✅ No immutable files were modified in this branch.

Changed Files

  • docs/design/media/landing-signature-loop.gif
  • docs/design/media/landing-signature-loop.mp4
  • docs/eval/OFFICIAL_BENCHMARK_READINESS.md
  • docs/eval/OFFICIAL_BENCHMARK_TASK_COVERAGE.md
  • docs/eval/OPENROUTER_CONVEX_BENCHMARK.md
  • docs/eval/agent-improvement-loop.md
  • docs/eval/agent-improvement-loop.svg
  • docs/eval/agent-improvement-loop/20260717T235647Z.json
  • docs/eval/agent-improvement-loop/latest.json
  • docs/eval/agent-workspace-sandbox-smoke.json
  • docs/eval/algorithm-artifact-smoke.json
  • docs/eval/bankertoolbench-official-contract.json
  • docs/eval/docker-sandbox-probe.json
  • docs/eval/eval-runs.jsonl
  • docs/eval/halo-convex-context-telemetry.json
  • docs/eval/halo-self-improvement-smoke.json
  • docs/eval/halo-variant-selection.json
  • docs/eval/official-benchmark-readiness.json
  • docs/eval/official-benchmark-task-coverage.json
  • docs/eval/openrouter-convex-benchmark.json
  • docs/eval/professional-catalog-proofs.json
  • docs/eval/professional-proof-ledger.json
  • docs/eval/spreadsheetbench-chart-visual-probe.json
  • docs/eval/traces/credit/20260717T235656508Z-f9b6bbdd_dirty.dc0e685ec3889a9c/cascade-healthy.json
  • docs/eval/traces/credit/20260717T235656508Z-f9b6bbdd_dirty.dc0e685ec3889a9c/delta-incomplete.json
  • docs/eval/traces/credit/20260717T235656508Z-f9b6bbdd_dirty.dc0e685ec3889a9c/mapping-correct.json
  • docs/eval/traces/credit/20260717T235656508Z-f9b6bbdd_dirty.dc0e685ec3889a9c/mapping-misbind.json
  • docs/eval/traces/credit/20260717T235656508Z-f9b6bbdd_dirty.dc0e685ec3889a9c/summit-stressed.json
  • docs/eval/traces/ladder/20260717T235656025Z-f9b6bbdd_dirty.e1d8c435c3589ba2/ladder_L1_read_scripted.json
  • docs/eval/traces/ladder/20260717T235656025Z-f9b6bbdd_dirty.e1d8c435c3589ba2/ladder_L2_edit_scripted.json
  • docs/eval/traces/ladder/20260717T235656025Z-f9b6bbdd_dirty.e1d8c435c3589ba2/ladder_L3_conflict_scripted.json
  • docs/eval/traces/ladder/20260717T235656025Z-f9b6bbdd_dirty.e1d8c435c3589ba2/ladder_L4_blocked_scripted.json
  • docs/eval/traces/ladder/20260717T235656025Z-f9b6bbdd_dirty.e1d8c435c3589ba2/ladder_L5_large_range_scripted.json
  • docs/eval/traces/ladder/20260717T235656025Z-f9b6bbdd_dirty.e1d8c435c3589ba2/ladder_L6_long_horizon_scripted.json
  • docs/eval/traces/ladder/20260717T235656025Z-f9b6bbdd_dirty.e1d8c435c3589ba2/ladder_L7_resume_scripted.json
  • e2e/modal-aria-hiding.spec.ts
  • e2e/ui-contract-drift.spec.ts
  • index.html
  • package.json
  • public/.well-known/agent-ui.json
  • scripts/ui-contract-emit.ts
  • src/app/styles.css
  • src/design/uiContract.ts
  • src/ui/Chat.tsx
  • src/ui/Landing.tsx
  • src/ui/RoomShell.tsx
  • src/ui/landing.css
  • src/ui/mobile/MobileScreens.tsx
  • src/ui/mobile/mobile.css
  • src/ui/mobile/mobileData.ts

Needs Adversarial Review — Do NOT Apply Yet

These proposals passed the reject check but have not been approved by
an adversarial reviewer. A human or frozen LLM judge must approve them first.

  • scaf-001 (AGENTS.md): Add explicit instruction for step spreadsheetbench-runner-fixture: Step spreadsheetbench-runner-fixture failed — scaffold may need explicit instruction or evidence assertion.
  • scaf-002 (AGENTS.md): Add explicit instruction for step convex-boundaries: Step convex-boundaries failed — scaffold may need explicit instruction or evidence assertion.

Safety Boundary

Agent may improve the scaffold.
Agent may NOT weaken the proof gate.

Immutable files (never modify):

  • scripts/proofloop.mjs
  • scripts/agent-improvement-loop.ts
  • tests/harnessChangeEval.test.ts
  • .github/workflows/
  • src/eval/evalTrustPolicy.ts
  • src/eval/architectureBudget.ts
  • evals/evalStore.ts

Scaffold files (safe to modify):

  • AGENTS.md
  • CLAUDE.md
  • proofloop/scenarios/*.yaml
  • proofloop/rubrics/*.yaml
  • proofloop/subagents/*.md
  • proofloop/adapters/*.js
  • .proofloop/memory.jsonl
  • src/nodeagent/models/prompts/systemPrompt.ts

…ent-ui.json

The app now publishes its own drivability contract instead of making agents
discover the UI by scraping:

- src/design/uiContract.ts — authored, versioned source of truth: surfaces
  (the data-noderoom-surface taxonomy), elements with declared contexts and
  actions, state transitions (palette open/close), invariants (each naming
  the spec that guards it), and executable journeys.
- scripts/ui-contract-emit.ts — deterministic emit to
  public/.well-known/agent-ui.json (served by the built app + Vercel);
  --check mode wired into design:audit so contract edits cannot land stale.
- e2e/ui-contract-drift.spec.ts — EXECUTES the served contract against the
  rendered DOM: fetches the JSON, completes journeys from the contract's own
  steps, resolves every element in its declared context (landing, room,
  paletteOpen state, mobile). Renamed testids now fail CI.

Differs from the existing `proofloop ui contract` (detectUiContracts), which
regex-infers a capped element list from source with guessed actions and no
rendered-DOM enforcement. First drift-verifier run already caught a contract
error: notifications-bell mounts only in convex mode (RoomShell gates it), so
it is declared live-only rather than memory-room-available.

Co-Authored-By: Claude Fable 5 <[email protected]>
… mobile card metadata its own row

Three composition fixes, no new tokens or visual language:

- index.html SSR shell: below 560px the header becomes brand row + one clean
  nav row at pill scale — previously the three nav pills wrapped raggedly
  beside the brand on phones.
- Landing.tsx / landing.css: the memory-mode trust line becomes the same
  neutral chip row as the SSR shell's proof points, so first paint and
  hydrated app read as one design (was a plain text run after hydration).
- Mobile recents cards: metadata gets its own full-width row (title -> meta ->
  action) instead of sharing a row with the open button, which ellipsized it
  to meaningless fragments ("deck ·…", "2…"); the redundant kind prefix is
  dropped since the kind badge already names it.

Verified: mobile suites green, design:audit + ui:contract:check pass, drift +
aria guards pass against the rebuilt preview; before/after captures in
.qa/evidence/20260717-postmerge/before-after/.

Co-Authored-By: Claude Fable 5 <[email protected]>
…s itself

Direction chosen from three scored candidates (atmospheric field rejected:
decorative motion violates quiet-UI; command-first composer rejected: product-
scope change, not a landing pass). Winner: amplify the existing 6-step
provenance loop into THE signature moment, with a motion identity where every
movement IS a ledger event — nothing decorative:

- Row focus sweep: when the agent acts on a row, the product's Trace Lens
  focus treatment draws across it (terracotta sweep + settling inset ring) —
  the in-app Attention Overlay motif, brought to the landing.
- The cited quote RULES ITSELF into the ledger: its underline draws left to
  right and persists.
- Lock badges clamp on with a small overshoot (a physical claim gesture).
- Committed values land with ink that dries (rise + decay), staged 160ms
  after the agent line; the version chip ticks LAST (+340ms) — commit first,
  then the ledger acknowledges.
- The agent avatar carries a soft working ring until the loop settles on v43.
- Hero type continuity: SSR paints up to 76px, hydration dropped to 44px —
  the page visibly shrank. Now clamp(36px, 4.8vw, 60px), lh 0.95.

All motion is gated behind prefers-reduced-motion: no-preference, matching
the file's existing idiom (reduced users get the settled final frame).
Gates: design:audit + ui:contract:check pass; drift/aria/landing e2e pass.
Filmstrip captures: .qa/evidence/20260717-postmerge/before-after/after-signature-*.png

Co-Authored-By: Claude Fable 5 <[email protected]>
Recorded from the built app (Playwright recordVideo), cropped to the demo
card, trimmed to precisely one 10.1s provenance loop using ffmpeg scene
detection (v43->lock flip at pts 10.80; next wrap at 20.92 confirms the
period). Opens on lock, closes on v43. MP4 (h264, 397KB) + GIF (15fps,
1.8MB) in docs/design/media/.

Co-Authored-By: Claude Fable 5 <[email protected]>
@HomenShum
HomenShum merged commit 65a08d4 into main Jul 18, 2026
11 checks passed
@HomenShum
HomenShum deleted the qa/frontend-verify-nitpick branch July 18, 2026 05:10
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.

Modal surfaces never mark #root aria-hidden — background stays in the a11y tree

1 participant