Skip to content

Design-system hardening, accessibility follow-through, and agent UI exemplars#131

Merged
jscraik merged 17 commits intomainfrom
codex/agent-ui-hardening-mar-2026
Mar 25, 2026
Merged

Design-system hardening, accessibility follow-through, and agent UI exemplars#131
jscraik merged 17 commits intomainfrom
codex/agent-ui-hardening-mar-2026

Conversation

@jscraik
Copy link
Copy Markdown
Owner

@jscraik jscraik commented Mar 25, 2026

Summary

  • harden the design-system guidance and policy gates used for agent-authored UI
  • migrate settings, chat, modal, page, and selected reusable UI surfaces onto semantic sizing, focus, and token-safe patterns
  • expand Storybook and web exemplar coverage with committed baselines and ratcheted learning surfaces
  • carry forward the accessibility, motion-token, security, and z-index follow-up commits already present on this branch

Validation

  • push hooks passed: lint, typecheck, and packages/ui tests
  • focused semantic-sizing assertion updates landed in a follow-up test commit

Current CI Blocker

  • build (ubuntu-latest) and build (macos-latest) are failing on pnpm sync:versions:check
  • reported mismatches:
    • packages/ui: 0.1.0 expected 0.0.1
    • packages/widgets: 0.0.2 expected 0.0.1
    • packages/cloudflare-template: 0.0.2 expected 0.0.1

Reviewer Notes

  • this PR is intentionally broad: it includes the full local-main stack that was ahead of origin/main, not only the final agent-UI hardening commit
  • the most important review surfaces are policy/guidance, protected settings and chat migrations, exemplar evaluation wiring, and the committed visual baselines

jscraik and others added 12 commits March 22, 2026 18:56
…FileUpload

- Add `reducedMotion` companion object to `packages/tokens/src/enhanced/motion.ts`
  with zero-duration per-token overrides and a `cssBlock` helper for
  `@media (prefers-reduced-motion: reduce)` rules
- Export `reducedMotion` from `packages/tokens/src/index.ts`
- Add `motion-reduce:animate-none` to all production spinner/pulse animations:
  Button, Checkbox, Switch, EmptyMessage, Indicator, ListItem, MessageActions,
  CodeBlock, Markdown, chart, Progress, AlertDialog, toast, FileUpload,
  RangeSlider, TagInput, ModeSelector, ModelBadge, ViewModeToggle, carousel,
  Sidebar, command, modal
- Wire `aria-live="polite"` + `aria-atomic="true"` status region to FileUpload
  so screen readers announce file selection and rejection results
- Add vendor-risk doc for Apps SDK UI dependency (`docs/risks/APPS_SDK_VENDOR_RISK.md`)
- Update design audit report (`reports/design-audit.html`) with resolved findings
- Fix Biome lint: template literals, arrow functions, optional chaining,
  font-family HTML escaping, empty CSS block, alignment whitespace, indentation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ock, RangeSlider, Pagination, SegmentedControl

Combobox
- Remove misused role="combobox" from trigger button (disclosure button pattern, not ARIA combobox)
- Add aria-controls linking trigger → listbox; add id on listbox element
- Add id on each option; add aria-activedescendant + aria-autocomplete="list" to search input
  so screen readers announce the highlighted option during keyboard navigation

TagInput
- Wrap in role="group" with aria-label for composite widget semantics
- Label the text input via aria-label; add aria-invalid + aria-describedby for error
- Add role="status" aria-live="polite" region to announce tag add/remove to screen readers
- Render inline error message element linked by aria-describedby

RangeSlider
- Move aria-invalid from wrapper div to the focusable <input type="range">
- Add aria-describedby on input pointing to rendered error paragraph
- Add visible error message element; remove stale aria-invalid from wrapper

Carousel
- Add aria-label (default "Content slideshow") to role="region" so it is a meaningful landmark

Pagination
- Explicit aria-hidden="true" on PaginationEllipsis (was bare aria-hidden)

SegmentedControl
- Wrap icon in aria-hidden="true" span to prevent double-announcement alongside label text

CodeBlock (line numbers)
- Change line-number <td> to <th scope="row"> for correct table semantics
- Add visually-hidden <caption> so AT announces the table purpose

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ements

Adds optional `index` (0-based) and `total` props to CarouselItem.
When both are provided, aria-label="N of total" is rendered on the
role="group" slide element so screen readers announce slide position
("1 of 5", "2 of 5", etc.) per the ARIA carousel pattern.

Existing usage without these props is unaffected — aria-label is only
applied when both values are present.

Usage:
  {items.map((item, i) => (
    <CarouselItem key={item.id} index={i} total={items.length}>
      ...
    </CarouselItem>
  ))}

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- TextLink: add sanitizeHref() with protocol allow-list (https, http, mailto,
  tel, relative paths). Dangerous protocols like javascript: and data: render
  as inert anchors (no href attribute).
- Markdown: apply sanitizeHref() at parse boundary before passing extracted
  URLs to TextLink — closes the XSS path through untrusted markdown content.
- Combobox: restore role="combobox" on trigger button (select-only combobox
  ARIA pattern) — fixes 31 test failures from incorrect a11y removal.
- Security report: reports/security_best_practices_report.md (OWASP A03).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Typography (baseline-ui rules):
- Markdown h1/h2/h3: add text-balance
- Markdown p: add text-pretty
- EmptyMessage h3: add text-balance
- EmptyMessage description p: add text-pretty
- ErrorBoundary h3: add text-balance
- Modal h2: add text-balance
- Card h4 (CardTitle): add text-balance via cn default

Layout:
- toast: max-h-screen → max-h-dvh (dynamic viewport height for mobile browsers)

Note: toast z-[100] flagged as a design-system concern (no built-in z-100
Tailwind token) — deferred to a separate design-system discussion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wraps the search state in useDeferredValue so the input field updates
immediately while the filtered options list can defer its re-render.
This keeps the combobox responsive when filtering large option lists.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a semantic zIndex extension to the Tailwind preset, backed by the
existing --ds-z-* CSS custom properties from enhanced.css. Enables use
of z-modal-backdrop, z-modal, z-popover, z-tooltip, etc. as Tailwind
utilities instead of arbitrary z-[n] values.

- toast: z-[100] → z-modal-backdrop (first component migrated)

Note: existing components using z-50 for overlays/popovers/tooltips
pre-date this scale and remain at z-50 for now. A broader migration
to z-popover / z-tooltip is tracked as a follow-up.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Documents that ChartStyle uses an unsafe innerHTML escape hatch,
which inputs are sanitized (id, color values, config keys), and
that ChartConfig must be developer-controlled — not populated from
untrusted user input without explicit sanitization.

Closes SEC-03 from the audit report.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- alert: add warning/success/info variants wired to existing status tokens
- alert: fix AlertTitle ref type (HTMLParagraphElement → HTMLHeadingElement)
- button: remove aria-invalid on <button> (invalid per ARIA spec)
- button: add micro-interactions (hover scale 1.01 / active scale 0.98) with motion-reduce overrides
- tokens: add transitionDuration and transitionTimingFunction to Tailwind preset (ds-micro → ds-complex, ds-swift/standard/ease-*)
- focus: scope focusVisibleCSS from bare :focus-visible to .ds-focusable / [data-ds-focusable] to prevent global style conflicts; add @warning JSDoc

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ecurity report

- testing/utils: pass `baseElement.ownerDocument` to userEvent.setup() instead of
  relying on globalThis.document, which user-event@14 captures at module load time
  (before jsdom initialises in vitest 4). Root cause: defaultOptionsDirect caches
  globalThis.document=undefined; our fix bypasses the cache using the live DOM.
- Add happy-dom devDep (installed, kept for optional future use)
- reports: append 2026-03-23 security review findings (S-01 to S-04) to existing report

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
  Wire the March 2026 agent-UI contract into policy, migrate protected settings
  and chat surfaces onto semantic design-system patterns, expand exemplar and
  visual coverage, and ratchet additional Storybook learning surfaces away from
  foundation-token usage.

  Co-authored-by: Codex <noreply@openai.com>
  EOF

  git pull --rebase origin main
  git switch -c codex/agent-ui-hardening-mar-2026
  git push -u origin codex/agent-ui-hardening-mar-2026
  gh pr create --fill --base main --head codex/agent-ui-hardening-mar-2026
  Update stale ChatSidebar, Button, Select, ScrollArea, and touch-target policy
  tests to match the new semantic sizing and focus-ring classes introduced by the
  design-system hardening pass.

  Co-authored-by: Codex <noreply@openai.com>
  EOF

  git push -u origin codex/agent-ui-hardening-mar-2026
  gh pr create --fill --base main --head codex/agent-ui-hardening-mar-2026
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 25, 2026

Required keyword not found in PR title or description.

@jscraik jscraik changed the title codex/agent ui hardening mar 2026 Design-system hardening, accessibility follow-through, and agent UI exemplars Mar 25, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9d02d747b2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +172 to +173
const mobileBreakpointRem = mobileBreakpointPx / 16;
const isMobile = useMediaQuery(`(max-width: ${mobileBreakpointRem}rem)`);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve mobileBreakpointPx in pixel units

mobileBreakpointPx is still the public prop name/default (768) but this change converts it to rem before building the media query. That makes the breakpoint depend on the root font size (e.g., 20px root turns 768 into a 960px breakpoint), so hosts that pass pixel values will see sidebar/mobile behavior switch at the wrong widths. Keep the query in px (or rename the API to rem) to avoid this regression.

Useful? React with 👍 / 👎.

}

const slug = pathname.replace("/template-widget/", "");
return slug ? decodeURIComponent(slug) : undefined;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Catch URI decode failures for template-widget slugs

This decodeURIComponent is executed during routing without error handling, so malformed percent-encoded paths (for example /template-widget/%E0%A4%A) will throw a URIError during render and crash the route instead of showing a fallback state. Wrap slug decoding in a try/catch and return undefined (or a safe fallback) on invalid input.

Useful? React with 👍 / 👎.

  Align packages/ui, packages/widgets, and packages/cloudflare-template back to
  the
  root workspace version so sync:versions:check passes again in CI.

  Co-authored-by: Codex <noreply@openai.com>
  EOF

  git push
@socket-security
Copy link
Copy Markdown

socket-security bot commented Mar 25, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedreact@​19.2.41001008497100

View full report

jscraik and others added 2 commits March 24, 2026 21:36
Pin Storybook Playwright visuals to en-US and UTC, and force the fullscreen
chat/settings exemplar stories onto the exact 1280x720 capture surface inside
storybook-visual.spec.ts so Ubuntu CI does not drift from the committed
fullscreen baselines because of runner locale, timezone, or dvh/overflow quirks.

Co-authored-by: Codex <noreply@openai.com>
Move the always-on exemplar evaluation step from the Ubuntu web build to the
macOS build lane so the Storybook and fullscreen chat/settings screenshots are
checked on the same platform their committed baselines were recorded on.

Co-authored-by: Codex <noreply@openai.com>
@socket-security
Copy link
Copy Markdown

socket-security bot commented Mar 25, 2026

All alerts resolved. Learn more about Socket for GitHub.

This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored.

View full report

  Keep the token regeneration path from clobbering the semantic elevation API in
  packages/tokens/src/shadows.ts. The DTCG source only defines the legacy shadow
  map, so sync-dtcg must re-emit elevation and ElevationToken or CI breaks after
  pnpm generate:tokens rewrites the file.

  Co-authored-by: Codex <noreply@openai.com>
  EOF

  git push
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 91e718fe28

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +123 to +125
"packages/ui/src/app/**/*.{ts,tsx,js,jsx,css}",
"platforms/web/apps/web/src/pages/**/*.{ts,tsx,js,jsx,css}",
"packages/ui/src/components/ui/**/*.{ts,tsx,js,jsx,css}",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make test/spec exemptions override warn scope globs

The new warn scopes are more specific than the broad **/*.test.* and **/*.spec.* exempt globs, and findBestScopeMatch chooses the most specific match before precedence; as a result, test files are still linted as warnings (for example Chart.test.tsx warnings are emitted by design-system-guidance:check:ci) despite being listed under scopes.exempt. This can cause touched-file ratchet failures on test-only changes that should be exempt.

Useful? React with 👍 / 👎.

  Use the stronger secondary text token for the template-browser description and
  the harness modal helper copy so the web Axe checks pass again in CI.

  Co-authored-by: Codex <noreply@openai.com>
  EOF

  git push
@jscraik jscraik merged commit 8b2f33c into main Mar 25, 2026
10 checks passed
@jscraik jscraik deleted the codex/agent-ui-hardening-mar-2026 branch March 25, 2026 05:46
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