Sheet, ErrorBanner, Skeleton and EmptyState #36

Member

Task: 2f9b64dd-0c26-47b9-b2b8-557bdf506d68 Sheet, banner, skeleton and empty-state primitives.

What changed: web/src/ui adds Sheet, ErrorBanner, Skeleton and EmptyState, exported from the UI index, styled with CSS Modules and design tokens only.

Why: Feature 05 task 3. Sheet is a labelled aria-modal dialog with focus move/trap/restore, Escape and scrim close, inert background, 320 ms rise using --ease-out-soft, and no rise under reduced motion. ErrorBanner is a neutral-100 card with accent-700 text and a Retry button. Skeleton is a surface pill with a 1.2 s pulse disabled under reduced motion. EmptyState uses the Bagel heading font and an optional primary action.

Done-condition: Vitest covers focus trap/restore, Escape, scrim close, Retry callback and reduced-motion behaviour. Local npm lint:colors, typecheck, test (16 files, 74 tests) and build passed; gofmt -l printed nothing. Not verified in a real browser.

Base: feature/64e5bb19-c9a2-4c6a-8f0c-aceae43fb732-frontend-foundation at 02f1dd6491.

Task: 2f9b64dd-0c26-47b9-b2b8-557bdf506d68 Sheet, banner, skeleton and empty-state primitives. What changed: web/src/ui adds Sheet, ErrorBanner, Skeleton and EmptyState, exported from the UI index, styled with CSS Modules and design tokens only. Why: Feature 05 task 3. Sheet is a labelled aria-modal dialog with focus move/trap/restore, Escape and scrim close, inert background, 320 ms rise using --ease-out-soft, and no rise under reduced motion. ErrorBanner is a neutral-100 card with accent-700 text and a Retry button. Skeleton is a surface pill with a 1.2 s pulse disabled under reduced motion. EmptyState uses the Bagel heading font and an optional primary action. Done-condition: Vitest covers focus trap/restore, Escape, scrim close, Retry callback and reduced-motion behaviour. Local npm lint:colors, typecheck, test (16 files, 74 tests) and build passed; gofmt -l printed nothing. Not verified in a real browser. Base: feature/64e5bb19-c9a2-4c6a-8f0c-aceae43fb732-frontend-foundation at 02f1dd6491a072fee429d6d5243dd33bb6a7d8e8.
dev_agent added 1 commit 2026-10-03 18:10:21 -04:00
Add Sheet, ErrorBanner, Skeleton and EmptyState primitives
Independent review Codex gpt-6-sol high: CHANGES_REQUIRED (1 high)
CI / ci (pull_request) Successful in 1m28s
CI / ci (push) Successful in 1m28s
1dd97b123c
Sheet is a portalled modal bottom sheet: role="dialog" with aria-modal
and a required accessible name, focus moved in, trapped and restored on
close, Escape and scrim click call onClose, and the rest of <body> is
inert while open. It rises from translateY(100%) over 320 ms
var(--ease-out-soft) over a scrim fading to 50% neutral-900; under
prefers-reduced-motion it opens in place.

ErrorBanner is a muted neutral-100 Card with grape-700 text and a Retry
Button; Skeleton is a --color-surface pill with a 1.2 s opacity pulse
switched off under reduced motion; EmptyState has a Bagel title, body
and an optional primary Button action.

jsdom never matches prefers-reduced-motion, resolves var() colours to
transparent and does not expand the animation shorthand, so the new
src/test/cssRules.ts reads the CSS Modules' declared rules, the
reduced-motion media block and the hashed @keyframes directly.

Maestro task: 2f9b64dd-0c26-47b9-b2b8-557bdf506d68

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
review_agent left a comment
Member

Independent review — Codex gpt-6-sol, reasoning high

Verdict: CHANGES_REQUIRED
Reviewed SHA: 1dd97b123c4cbcab91197eb5b4d925b620baa25e

{
  "verdict": "CHANGES_REQUIRED",
  "reviewed_sha": "1dd97b123c4cbcab91197eb5b4d925b620baa25e",
  "summary": "The primitives meet the styling and interaction requirements inspected, and color lint passes. Typecheck, Vitest and build could not run because web/node_modules is absent. One Sheet focus failure leaves the done-condition unmet.",
  "findings": [
    {
      "id": "MQ-36-01",
      "severity": "high",
      "location": "web/src/ui/Sheet.tsx:16-26,46",
      "failure_case": "If the sheet contains a hidden button before a visible control, opening it does not move focus into the dialog. Tab trapping then cannot work from the opener.",
      "evidence": "focusables() selects button:not([disabled]) without excluding hidden or otherwise unfocusable elements. The opening effect calls focus() on the first match and does not check whether focus moved.",
      "suggested_correction": "Select only controls that can actually receive keyboard focus, fall back to focusing the panel, and add a test with a hidden first control."
    }
  ]
}

Posted unaltered by review_agent. This is transport, not a second review.

## Independent review — Codex `gpt-6-sol`, reasoning high Verdict: **CHANGES_REQUIRED** Reviewed SHA: `1dd97b123c4cbcab91197eb5b4d925b620baa25e` ```json { "verdict": "CHANGES_REQUIRED", "reviewed_sha": "1dd97b123c4cbcab91197eb5b4d925b620baa25e", "summary": "The primitives meet the styling and interaction requirements inspected, and color lint passes. Typecheck, Vitest and build could not run because web/node_modules is absent. One Sheet focus failure leaves the done-condition unmet.", "findings": [ { "id": "MQ-36-01", "severity": "high", "location": "web/src/ui/Sheet.tsx:16-26,46", "failure_case": "If the sheet contains a hidden button before a visible control, opening it does not move focus into the dialog. Tab trapping then cannot work from the opener.", "evidence": "focusables() selects button:not([disabled]) without excluding hidden or otherwise unfocusable elements. The opening effect calls focus() on the first match and does not check whether focus moved.", "suggested_correction": "Select only controls that can actually receive keyboard focus, fall back to focusing the panel, and add a test with a hidden first control." } ] } ``` --- Posted unaltered by `review_agent`. This is transport, not a second review.
dev_agent added 1 commit 2026-10-03 18:14:39 -04:00
Move Sheet focus only to controls that can take keyboard focus
CI / ci (push) Successful in 1m30s
CI / ci (pull_request) Successful in 1m29s
0fa9cfe611
Review finding MQ-36-01: a hidden first button was chosen as the initial
focus target and as a Tab trap stop, so focus never entered the dialog.
Filter out controls with a negative tabIndex, a hidden/aria-hidden/inert
ancestor, display: none or visibility: hidden; on open, skip any control
whose focus() does not take, falling back to the dialog panel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dev_agent added 1 commit 2026-10-03 18:17:48 -04:00
Leave controls disabled by a fieldset out of the Sheet focus trap
Independent review Codex gpt-6-sol high: APPROVED (0 findings)
CI / ci (push) Successful in 1m28s
CI / ci (pull_request) Successful in 1m28s
ef3e7d0e45
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
review_agent left a comment
Member

Independent review — Codex gpt-6-sol, reasoning high

Verdict: APPROVED
Reviewed SHA: ef3e7d0e4582d9294376f23a733cc4dc69f38cb7

{
  "verdict": "APPROVED",
  "reviewed_sha": "ef3e7d0e4582d9294376f23a733cc4dc69f38cb7",
  "summary": "Static review found the requested primitives and behaviors, including the fixes for hidden controls and disabled fieldsets in the Sheet focus trap. Reduced-motion tests inspect stylesheet rules. `npm run lint:colors` passed. Typecheck, tests, and build could not be run because web/node_modules is absent; no packages were installed.",
  "findings": []
}

Posted unaltered by review_agent. This is transport, not a second review.

## Independent review — Codex `gpt-6-sol`, reasoning high Verdict: **APPROVED** Reviewed SHA: `ef3e7d0e4582d9294376f23a733cc4dc69f38cb7` ```json { "verdict": "APPROVED", "reviewed_sha": "ef3e7d0e4582d9294376f23a733cc4dc69f38cb7", "summary": "Static review found the requested primitives and behaviors, including the fixes for hidden controls and disabled fieldsets in the Sheet focus trap. Reduced-motion tests inspect stylesheet rules. `npm run lint:colors` passed. Typecheck, tests, and build could not be run because web/node_modules is absent; no packages were installed.", "findings": [] } ``` --- Posted unaltered by `review_agent`. This is transport, not a second review.
approver_agent merged commit 732aa92a8c into feature/64e5bb19-c9a2-4c6a-8f0c-aceae43fb732-frontend-foundation 2026-10-03 18:23:00 -04:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Maniacs_Mansion/MealQuest#36