diff --git a/.design-system-guidance.json b/.design-system-guidance.json new file mode 100644 index 00000000..18eed263 --- /dev/null +++ b/.design-system-guidance.json @@ -0,0 +1,147 @@ +{ + "schemaVersion": 1, + "docs": [ + "docs/design-system/CONTRACT.md", + "docs/design-system/PROFESSIONAL_UI_CONTRACT.md", + "docs/design-system/AGENT_UI_ROUTING.md" + ], + "include": [ + "packages/ui/src/app", + "platforms/web/apps/web/src/pages", + "packages/ui/src/components/ui", + "packages/ui/src/design-system/showcase", + "packages/ui/src/storybook", + "packages/ui/src/styles" + ], + "ignore": [ + "node_modules", + "dist", + ".git", + "coverage", + ".next", + "build" + ], + "scopes": { + "error": [ + "packages/ui/src/app/settings/AppsPanel/AppsPanel.stories.tsx", + "packages/ui/src/app/settings/AppsPanel/AppsPanel.tsx", + "packages/ui/src/app/settings/ArchivedChatsPanel/ArchivedChatsPanel.stories.tsx", + "packages/ui/src/app/settings/ArchivedChatsPanel/ArchivedChatsPanel.tsx", + "packages/ui/src/app/settings/AudioSettingsPanel/AudioSettingsPanel.stories.tsx", + "packages/ui/src/app/settings/AudioSettingsPanel/AudioSettingsPanel.tsx", + "packages/ui/src/app/settings/CheckForUpdatesPanel/CheckForUpdatesPanel.stories.tsx", + "packages/ui/src/app/settings/CheckForUpdatesPanel/CheckForUpdatesPanel.tsx", + "packages/ui/src/app/settings/DataControlsPanel/DataControlsPanel.stories.tsx", + "packages/ui/src/app/settings/DataControlsPanel/DataControlsPanel.tsx", + "packages/ui/src/app/settings/ManageAppsPanel/ManageAppsPanel.stories.tsx", + "packages/ui/src/app/settings/ManageAppsPanel/ManageAppsPanel.tsx", + "packages/ui/src/app/settings/NotificationsPanel/NotificationsPanel.stories.tsx", + "packages/ui/src/app/settings/NotificationsPanel/NotificationsPanel.tsx", + "packages/ui/src/app/settings/PersonalizationPanel/PersonalizationPanel.stories.tsx", + "packages/ui/src/app/settings/PersonalizationPanel/PersonalizationPanel.tsx", + "packages/ui/src/app/settings/SecurityPanel/SecurityPanel.stories.tsx", + "packages/ui/src/app/settings/SecurityPanel/SecurityPanel.tsx", + "packages/ui/src/app/settings/SettingDropdown/SettingDropdown.stories.tsx", + "packages/ui/src/app/settings/SettingDropdown/SettingDropdown.tsx", + "packages/ui/src/app/settings/SettingRow/SettingRow.stories.tsx", + "packages/ui/src/app/settings/SettingRow/SettingRow.tsx", + "packages/ui/src/app/settings/SettingToggle/SettingToggle.stories.tsx", + "packages/ui/src/app/settings/SettingToggle/SettingToggle.tsx", + "packages/ui/src/app/settings/shared/**/*.tsx", + "packages/ui/src/app/chat/AttachmentMenu/AttachmentMenu.stories.tsx", + "packages/ui/src/app/chat/AttachmentMenu/AttachmentMenu.tsx", + "packages/ui/src/app/chat/ChatInput/ChatInput.stories.tsx", + "packages/ui/src/app/chat/ChatInput/ChatInput.tsx", + "packages/ui/src/app/chat/ChatHeader/ChatHeader.stories.tsx", + "packages/ui/src/app/chat/ChatHeader/ChatHeader.tsx", + "packages/ui/src/app/chat/ChatMessages/ChatMessages.stories.tsx", + "packages/ui/src/app/chat/ChatMessages/ChatMessages.tsx", + "packages/ui/src/app/chat/ChatShell/ChatShell.stories.tsx", + "packages/ui/src/app/chat/ChatVariants/ChatVariants.tsx", + "packages/ui/src/app/chat/ChatView/ChatView.tsx", + "packages/ui/src/app/chat/ChatUIRoot/ChatUIRoot.stories.tsx", + "packages/ui/src/app/chat/ChatUIRoot/ChatUIRoot.tsx", + "packages/ui/src/app/chat/compose/ComposeInstructionsPanel/ComposeInstructionsPanel.tsx", + "packages/ui/src/app/chat/compose/ProEditConfigModal/ProEditConfigModal.tsx", + "packages/ui/src/app/chat/compose/PromptBuilderSection/PromptBuilderSection.tsx", + "packages/ui/src/app/chat/ComposeView/ComposeView.stories.tsx", + "packages/ui/src/app/chat/ComposeView/ComposeView.tsx", + "packages/ui/src/app/modals/DiscoverySettingsModal/DiscoverySettingsModal.stories.tsx", + "packages/ui/src/app/modals/DiscoverySettingsModal/DiscoverySettingsModal.tsx", + "packages/ui/src/app/modals/IconPickerModal/IconPickerModal.stories.tsx", + "packages/ui/src/app/modals/IconPickerModal/IconPickerModal.tsx", + "packages/ui/src/app/modals/SettingsModal/SettingsModal.stories.tsx", + "packages/ui/src/app/modals/SettingsModal/SettingsModal.tsx", + "packages/ui/src/app/modals/settings/**/*.tsx", + "packages/ui/src/app/chat/ChatSidebar/ChatSidebar.stories.tsx", + "packages/ui/src/app/chat/ChatSidebar/ChatSidebar.tsx", + "packages/ui/src/app/chat/ChatSidebar/components/ChatSidebarHistory/ChatSidebarHistory.stories.tsx", + "packages/ui/src/app/chat/ChatSidebar/components/ChatSidebarHistory/ChatSidebarHistory.tsx", + "packages/ui/src/app/chat/ChatSidebar/modals/NewProjectModal/NewProjectModal.stories.tsx", + "packages/ui/src/app/chat/ChatSidebar/modals/NewProjectModal/NewProjectModal.tsx", + "packages/ui/src/app/chat/ChatSidebar/modals/ProjectSettingsModal/ProjectSettingsModal.stories.tsx", + "packages/ui/src/app/chat/ChatSidebar/modals/ProjectSettingsModal/ProjectSettingsModal.tsx", + "packages/ui/src/components/ui/navigation/NavigationMenu/fallback/NavigationMenu.tsx", + "packages/ui/src/components/ui/navigation/sidebar/fallback/Sidebar.tsx", + "packages/ui/src/components/ui/navigation/tabs/fallback/Tabs.tsx", + "packages/ui/src/components/ui/navigation/ModeSelector/ModeSelector.tsx", + "packages/ui/src/components/ui/navigation/ModelSelector/ModelSelector.tsx", + "packages/ui/src/components/ui/base/IconButton/IconButton.tsx", + "packages/ui/src/components/ui/base/button/fallback/Button.tsx", + "packages/ui/src/components/ui/base/InputOTP/InputOTP.tsx", + "packages/ui/src/components/ui/base/RadioGroup/fallback/RadioGroup.tsx", + "packages/ui/src/components/ui/base/ScrollArea/fallback/ScrollArea.tsx", + "packages/ui/src/components/ui/base/checkbox/fallback/Checkbox.tsx", + "packages/ui/src/components/ui/base/input/input.tsx", + "packages/ui/src/components/ui/base/select/fallback/Select.tsx", + "packages/ui/src/components/ui/base/switch/fallback/Switch.tsx", + "packages/ui/src/components/ui/base/table/table.tsx", + "packages/ui/src/components/ui/base/textarea/textarea.tsx", + "packages/ui/src/components/ui/data-display/card/card.tsx", + "packages/ui/src/components/ui/feedback/ErrorBoundary/ErrorBoundary.tsx", + "packages/ui/src/components/ui/feedback/toast/toast.tsx", + "packages/ui/src/components/ui/overlays/command/command.tsx", + "packages/ui/src/components/ui/overlays/drawer/drawer.tsx", + "packages/ui/src/components/ui/overlays/modal/modal.tsx", + "packages/ui/src/components/ui/overlays/tooltip/fallback/Tooltip.tsx", + "packages/ui/src/storybook/_holding/component-stories/Chart.stories.tsx", + "packages/ui/src/storybook/_holding/component-stories/IconButton.stories.tsx", + "packages/ui/src/storybook/_holding/component-stories/MessageActions.stories.tsx", + "packages/ui/src/storybook/_holding/component-stories/NavigationMenu.stories.tsx", + "packages/ui/src/storybook/_holding/docs/APIReference.mdx", + "packages/ui/src/storybook/_holding/docs/DesignSystem.mdx", + "packages/ui/src/storybook/_holding/docs/GettingStarted.mdx", + "packages/ui/src/storybook/_holding/docs/Migration.mdx", + "packages/ui/src/storybook/_holding/docs/Patterns.mdx", + "packages/ui/src/storybook/_holding/docs/QuickStart.mdx", + "platforms/web/apps/web/src/pages/ChatShellPage.tsx", + "platforms/web/apps/web/src/pages/HarnessPage.tsx", + "platforms/web/apps/web/src/pages/TemplateBrowserPage.tsx", + "platforms/web/apps/web/src/pages/TemplateWidgetPage.tsx" + ], + "warn": [ + "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}", + "packages/ui/src/design-system/showcase/**/*.{ts,tsx,js,jsx,css}", + "packages/ui/src/storybook/**/*.{ts,tsx,js,jsx,css}", + "**/*.stories.tsx" + ], + "exempt": [ + "packages/tokens/**", + "packages/ui/src/styles/theme.css", + "docs/design-system/**", + "**/*.test.{ts,tsx,js,jsx}", + "**/*.spec.{ts,tsx,js,jsx}", + "docs/design-system/COVERAGE_MATRIX.json", + "docs/design-system/COVERAGE_MATRIX.md", + "docs/design-system/COVERAGE_MATRIX_SURFACES.json" + ] + }, + "exemptionLedger": "docs/design-system/ENFORCEMENT_EXEMPTIONS.json", + "scopePrecedence": [ + "error", + "warn", + "exempt" + ] +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 67f710ef..7e51c4ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -129,8 +129,8 @@ jobs: if: matrix.platform == 'web' run: pnpm test:mcp-contract - - name: Install Playwright (web platform only) - if: matrix.platform == 'web' + - name: Install Playwright (web + macOS exemplar lanes) + if: matrix.platform == 'web' || matrix.platform == 'macos' run: | if [ "${{ runner.os }}" = "Linux" ]; then pnpm exec playwright install --with-deps chromium @@ -138,6 +138,10 @@ jobs: pnpm exec playwright install chromium fi + - name: Exemplar evaluation (macOS baseline lane) + if: matrix.platform == 'macos' + run: pnpm test:exemplar-evaluation + - name: Enhanced build pipeline run: node scripts/build-pipeline.mjs --platforms ${{ matrix.platform }} diff --git a/.gitignore b/.gitignore index d3c27991..91fcb356 100644 --- a/.gitignore +++ b/.gitignore @@ -80,6 +80,8 @@ screenshots/ **/storybook-static/ **/test-results/ **/playwright-report/ +**/node_modules/.vite/ +**/node_modules/.tmp/ # Misc .cache/ @@ -106,6 +108,9 @@ Thumbs.db ops/metrics/skill-feedback/*.jsonl # Internal docs (do not commit) docs/transcripts/ +# Local audit/run artifacts +reports/audit-report.md +reports/audit-screenshot-*.png # Project memory doc (FORJAMIE.md) is tracked in git. diff --git a/FORJAMIE.md b/FORJAMIE.md index f4e62b6e..a387127f 100644 --- a/FORJAMIE.md +++ b/FORJAMIE.md @@ -16,16 +16,17 @@ ## Status -**Last updated:** 2026-03-10 +**Last updated:** 2026-03-24 **Production status:** IN_PROGRESS -**Overall health:** Needs attention +**Overall health:** Yellow | Area | Status | Notes | | --- | --- | --- | -| Build / CI | Green baseline | `origin/main` was green before this local change-set | -| Tests | Needs re-run | Re-run after dependency pin/doc sync in this change-set | -| Open PRs | 0 | No open PRs before this workflow | -| Blockers | None | Local repo state only | +| Build / CI | Yellow | Root design-system policy now blocks on token truth, coverage freshness, and repo-wide guidance; broader warn backlog still exists | +| Tests | Yellow | `pnpm -C packages/ui exec vitest run src/app/settings/settings-panels.exemplar.test.tsx` passes; the broader `pnpm design-system-guidance:check:ci` command currently exits on the known repo-wide warn backlog | +| Security | Clean | 13 CVEs patched; GitHub Actions SHA-pinned | +| Open PRs | 0 | | +| Blockers | None | | ## TL;DR @@ -50,7 +51,7 @@ flowchart LR - `packages/runtime` provides host adapters and environment wiring for embedded and standalone usage. - `packages/widgets` packages standalone widget bundles used by the web surface and MCP integration paths. - `platforms/mcp` contains the MCP integration harness and tool contract tests. -- `packages/design-system-guidance` packages the policy and guidance checks used to keep downstream consumers aligned. +- `packages/design-system-guidance` packages the policy and guidance checks used to keep downstream consumers aligned, including repo-scoped protected-surface enforcement and an exemption ledger. ## Codebase map @@ -71,7 +72,9 @@ flowchart LR | --- | --- | --- | | Core package structure | Done | Tokens, UI, runtime, widgets, icons, and MCP/web surfaces are all established | | Adoption and guidance docs | Done | Onboarding command center, release docs, and governance docs exist | -| Guidance package | Done | `@brainwav/design-system-guidance` is present for downstream policy checks | +| Guidance package | Done | `@brainwav/design-system-guidance` now supports repo-scoped `error`/`warn`/`exempt` enforcement, path specificity, and exemption-ledger validation | +| Protected settings migration | In progress | The initial settings wave is migrated onto shared shell/composition patterns, with explicit state stories and jsdom exemplar tests for the protected settings slice; broader app/storybook warn backlog remains intentionally non-blocking | +| Visual regression workflow | In progress | Root visual scripts now route through `scripts/run-playwright-suite.mjs` and `packages/ui build:visual`, and the exemplar gate now covers both the template browser shell and an isolated template-widget shell route | | Current dependency hygiene | In progress | This change-set pins `vitest-axe` back to `1.0.0-pre.3` for deterministic installs | | Long-term debt cleanup | In progress | Lint, icon a11y, and docs maintenance remain ongoing work | @@ -91,10 +94,18 @@ pnpm test pnpm lint pnpm format:check pnpm build -pnpm test:deep +pnpm test:policy +pnpm test:exemplar-evaluation:list +pnpm test:exemplar-evaluation:update +pnpm design-system-guidance:ratchet +pnpm test:visual:web -- --list +pnpm test:visual:storybook -- --list ``` -`test:deep` is expected by the broader Codex workflow when source or runtime behavior changes. If that script is unavailable or fails for an unrelated reason, record the exact outcome in the PR body. +The visual suites now go through `scripts/run-playwright-suite.mjs`, which prebuilds `packages/ui` with `pnpm -C packages/ui build:visual` before handing the remaining args straight to Playwright. Use that path when you need reliable `--list`, `--grep`, or `--update-snapshots` behavior from the root scripts without leaving the package in a declaration-broken state. +The policy-owned exemplar slice now has its own root entry points. Use `pnpm test:exemplar-evaluation:list` to inspect the exact protected Storybook checks, and `pnpm test:exemplar-evaluation:update` to refresh only those baselines instead of hand-copying long `PLAYWRIGHT_STORYBOOK_*` env strings. `TemplateBrowserPage` is now part of the always-on exemplar slice, but that browser-backed check runs as an explicit exemplar gate rather than as part of the browser-free `pnpm test:policy` contract. +For template-family QA, use `/templates` for the searchable browser shell and `/template-widget/` for the isolated single-template widget shell that mirrors widget-style rendering without needing `VITE_TEMPLATE_ID`. +Committed visual baselines live under `tests/visual/__snapshots__`. Local Playwright HTML reports, nested Vite cache outputs under `node_modules/.vite`, nested `.tmp` build scratch files, and ad hoc `reports/audit-*` outputs are now gitignored so browser runs stop polluting the branch. ## Learnings & gotchas @@ -103,6 +114,24 @@ See also: `~/.codex/instructions/Learnings.md` - Keep `FORJAMIE.md` detailed and current whenever behavior, structure, config, or tooling changes. - Pre-release dependencies should stay pinned when the repo depends on exact lockfile behavior across workspaces. - Use the repo preflight helper before path-sensitive or multi-step git workflows. +- Guidance-policy exemptions must stop applying the moment they expire; otherwise CI reports the ledger drift but still hides the real surface-level rule failure that needs cleanup. +- Local Memory preflight probes need to honor the configured `rest_api.host` for every REST smoke request, not just health checks, or valid non-loopback setups look broken. +- The exemplar-evaluation gate depends on Playwright browser binaries being installed locally; if `pnpm test:exemplar-evaluation` fails before running tests, verify Chromium is provisioned for Playwright in this environment. `pnpm test:policy` should stay browser-free so pre-Playwright CI and release workflows do not fail on unrelated changes. +- `TemplateWidgetPage` only rendered during `VITE_TEMPLATE_ID` widget builds before this slice. The stable runtime route for browser QA is now `/template-widget/`, which gives the web visual suite a deterministic way to protect the isolated widget shell. +- Protected settings stories are inside the same repo-governed surface family as the app panels, so even Storybook-only scaffolding should avoid literal background hex values or other policy escapes that would add noisy guidance debt. +- `TemplateBrowserPage` is now part of the default exemplar gate. If `pnpm test:exemplar-evaluation` fails on the template-browser slice, treat that as a real always-on page regression rather than an opt-in baseline workflow. +- The root visual scripts are now the canonical entry point for listing or filtering Playwright coverage. They use the `packages/ui build:visual` path, which preserves declaration output while still avoiding the earlier visual-only typegen noise from internal Storybook demo code. +- The guidance CLI now supports `--json` for policy tooling, and large guidance payloads must be flushed before exit. If a future automation sees truncated JSON around 64 KB, check the CLI output path before blaming the policy consumer. +- If Storybook or web visual runs suddenly explode into dozens of identical Playwright launch failures, the likely problem is a missing local browser binary rather than dozens of broken stories. The suite wrapper now preflights Chromium for non-`--list` runs and prints the single remediation command. +- If you need to refresh the protected settings exemplar baselines, do not paste the long `PLAYWRIGHT_STORYBOOK_STORY_IDS=...` command by hand. Use `pnpm test:exemplar-evaluation:update` so the update path stays aligned with the policy runner's canonical `components-settings-*` story IDs. +- Storybook screenshot URLs must set both `globals.theme` and `backgrounds.value`, and they must derive their base URL from `PLAYWRIGHT_STORYBOOK_BASE_URL` / `PLAYWRIGHT_STORYBOOK_PORT` instead of hard-coding `localhost:6006`. Otherwise “light” screenshots can silently render the dark theme, and reused non-default Storybook servers will fail. +- Storybook visual captures now disable Agentation and Dialkit overlays by URL flag during Playwright runs. If a visual failure suddenly shows the MCP/annotation panel inside the screenshot, the preview/test URL contract has drifted and should be repaired before blessing any baseline. +- Storybook visual regression now runs single-worker with a 90s test timeout. That is intentional: the settings exemplar slice proved flaky under local parallel capture and late dark-theme sweeps, so determinism now wins over speed for this gate. +- Storybook visual captures now pin Playwright to `en-US` / `UTC`, and fullscreen exemplar stories are forced onto the exact `1280x720` capture surface inside the spec harness. If Ubuntu-only screenshot drift shows up on chat/settings fullscreen stories, check the visual harness before re-blessing baselines. +- The exemplar visual gate now belongs to the macOS build lane in CI, not the Ubuntu web lane. The committed Storybook/fullscreen baselines are currently mac-recorded, so running the always-on exemplar check on macOS keeps the gate trustworthy while Linux still handles policy/build coverage. +- Storybook dev must ignore Playwright artifact paths during visual runs. If the server starts reloading or dropping mid-capture, check `.storybook/main.ts` first and confirm `playwright-report`, `test-results`, and `__snapshots__` are excluded from Vite watch globs so the visual gate is not watching its own outputs. +- The touched-file ratchet must include untracked files via `git ls-files --others --exclude-standard`, not just tracked diffs. Otherwise a brand-new protected-surface file can dodge the ratchet until it is staged. +- Keep the committed visual baselines, but ignore local runtime artifacts. The repo now explicitly ignores nested `node_modules/.vite`, nested `node_modules/.tmp`, Playwright HTML reports, and `reports/audit-*` outputs so UI verification runs do not turn branch state into cache noise. ## Weaknesses & improvements @@ -110,9 +139,69 @@ See also: `~/.codex/instructions/Learnings.md` - `FORJAMIE.md` had started drifting toward a placeholder template; keep it as a real project map, not just a status shell. - The repo still carries known lint and accessibility debt that should be paid down incrementally as touched areas are updated. - Cross-project adoption used to look more flexible than it really was; keep README and checklist guidance aligned with the actual exported package surface and supported install modes. +- Design-system trust is weaker than it should be because token validation and coverage freshness can fail while broader policy checks still pass; align root policy with semantic-slot and design-system integrity rules before asking agents to generate more product UI. +- The repo-wide guidance command now passes, but it still reports a large `warn` backlog across reusable UI, storybook, and showcase surfaces. That backlog is visible by design and should be ratcheted down incrementally instead of hidden. +- The web and Storybook visual suites are finally deterministic enough to build on, and the first touched-file ratchet is now in place for the initial protected surface set. The remaining work is widening that ratchet gradually without turning warning debt back into hidden drift. +- The first touched-file ratchet now covers the full settings panel/story family, the touched forms component slice, and app pages including both template pages. It should keep expanding gradually, but only after adjacent surfaces stay clean long enough that the next boundary is believable. +- `packages/ui` still carries some internal Storybook `_holding` demo code that looks like package-consumer code. Keep that material out of declaration generation, or it will reintroduce self-import type noise into otherwise healthy package builds. +- `packages/tokens/src/shadows.ts` is generated, but it cannot be a pure mirror of the DTCG shadow source. The DTCG file only defines the legacy `card/pip/pill/close` map; the generated TypeScript layer must also re-emit the semantic `elevation` API and `ElevationToken` type or `packages/tokens/src/index.ts` will compile locally until the next `pnpm generate:tokens`, then break in CI. ## Recent changes +### 2026-03-23 + +- **Visual build packaging fix**: `packages/ui/package.json` no longer strips declaration generation from `build:visual`; it now uses the same safe `vite build` path as the canonical package build, and `packages/ui/tsconfig.dts.json` explicitly excludes `src/storybook/_holding/**` so internal Storybook demo pages do not leak into public declaration generation. That fixes the review regression where browser-test prebuilds could leave `@design-studio/ui` effectively untyped and also removes the non-fatal `TS7016` self-import warning from package builds. +- **Web app page-surface expansion**: `ChatShellPage` and `HarnessPage` are the next hard-error wave after the template family. `ChatShellPage` now uses the semantic `min-h-dvh bg-background text-foreground` shell, and `HarnessPage` has been rewritten away from foundation-token literals and `h-screen` into the same semantic page-shell language as the template surfaces. The exemplar runner now treats chat default, harness, and template browser/widget pages as one small web-app exemplar set. +- **Chat surface ratchet expansion**: the next enforcement wave now covers the chat surfaces that teach the shell internals: `AttachmentMenu`, `ChatInput`, and `ChatSidebar`, plus their stories. This pass replaced pixel-bound typography and size literals with semantic text roles, scale classes, or rem-based textarea sizing, and it swapped the remaining story `h-screen`/raw color wrappers for semantic shells so the touched-file ratchet can safely include this chat slice. +- **Chat sidebar sibling ratchet**: widened the hard-error and touched-file ratchet boundary one level deeper into the chat shell internals by adding `ChatSidebarHistory`, `NewProjectModal`, and `ProjectSettingsModal`. The only direct cleanup needed in this slice was replacing the history header’s raw `text-[11px]` escape with the semantic `text-caption` role; the two modal surfaces were already clean enough to ratchet in without adding more visual churn. +- **Chat sidebar exemplar coverage**: added direct Storybook states for `ChatSidebarHistory`, `NewProjectModal`, and `ProjectSettingsModal`, plus `loading` and `error` states and a modal-transition interaction flow on `ChatSidebar` itself. The exemplar runner now includes a small chat-sidebar internal slice alongside the protected settings stories, and the new story files are part of the same hard-error + touched-file ratchet boundary so the coverage path does not reintroduce story-level drift. +- **Chat content-surface ratchet**: widened the next hard-error wave to the `ChatHeader`, `ChatMessages`, and `ComposeView` slice plus their stories. This pass removed the remaining raw pixel typography and popup sizing from `ChatHeader`, replaced the assistant message bubble’s literal corner radius with a token-scale class in `ChatMessages`, and moved the chat-content story wrappers onto semantic `min-h-dvh bg-background text-muted-foreground` shells so the slice can teach the same recipe language as the cleaned sidebar and page shells. +- **Chat outer-shell ratchet**: widened the protected chat boundary to `ChatUIRoot` and `ChatShell.stories`. This pass keeps the existing `mobileBreakpointPx` API stable but converts the media-query implementation to a rem-based shell breakpoint, replaces `min-h-screen` with `min-h-dvh` in both the component and the story wrappers, and swaps the remaining raw accent-variable demo buttons for semantic `bg-accent-blue/20 text-accent-blue` story controls. +- **Chat outer-shell exemplar coverage**: added policy-owned Storybook exemplars for the cleaned outer chat shell layer. `ChatUIRoot` now exposes explicit `loading`, `error`, and full-overlay-open states alongside the default shell, `ChatShell` contributes a split-sidebar layout reference, and `scripts/policy/run-exemplar-evaluation.mjs` includes those story IDs plus a basic focus-state check on `ChatUIRoot` so the now-ratcheted shell layer is protected by the same visual gate as the settings and sidebar slices. +- **Chat view-layer ratchet**: widened the next protected chat boundary to `ChatVariants` and `ChatView`. This pass replaces the remaining literal right-rail width and composer scroll offsets with design-system scale classes (`w-80`, `pb-44`, `bottom-48`) so the cleaned outer shell and immediate content layer now share the same hard-error and touched-file ratchet path. +- **Compose internals ratchet**: widened the protected compose slice behind `ComposeView` to `ComposeInstructionsPanel`, `ProEditConfigModal`, and `PromptBuilderSection`. This pass removes the remaining pixel-locked heights, widths, and modal sizing (`187px`, `720px`, `200px`, `320px`, `120px`, `32px`) in favor of design-system scale or rem-based values so the compose path no longer drops back to legacy layout literals behind an otherwise clean chat shell. +- **Modal-family ratchet**: widened the next protected modal wave to `DiscoverySettingsModal`, `IconPickerModal`, and `SettingsModal`, plus their Storybook files. This pass removes `h-screen` wrappers, px-based modal widths/radii/title styles, and old CSS-variable story wrappers so the chat-adjacent modal path now teaches the same `min-h-dvh`, semantic text-role, and scale-based sizing language as the protected settings and chat surfaces. +- **Settings section-family ratchet**: widened the protected settings-modal internals to `packages/ui/src/app/modals/settings/**/*.tsx`. The only cleanup needed was replacing `AppSection`’s raw dropdown widths with scale classes (`min-w-50`, `min-w-36`), which confirms the rest of the main settings section family was already clean enough to promote under the hard-error and touched-file ratchet path. +- **Navigation selector ratchet**: widened the touched-file ratchet into the shared navigation selector layer by normalizing raw widths/heights in `ModeSelector` and `ModelSelector`. The selectors now use rem or scale-based sizing (`w-[60rem]`, `w-[25rem]`, `w-[22.5rem]`, `w-80`, `w-72`, `max-h-80`) and semantic `text-caption` badge text, so this reusable selector slice can move into the protected shared-UI set without reintroducing px-bound popup sizing. +- **Navigation fallback ratchet**: widened the shared reusable-UI ratchet into the fallback navigation primitives: `NavigationMenu`, `Sidebar`, and `Tabs`. This pass removes the remaining pixel-bound focus-ring widths, icon offsets, sidebar calc padding, resize-rail width, and tab shell sizing so the next reusable navigation layer can move into the protected set without carrying raw px escapes. +- **Overlay primitive ratchet**: widened the shared reusable-UI ratchet into the overlay primitive layer by normalizing the remaining pixel-bound sizes in `command`, `drawer`, `modal`, and fallback `Tooltip`. This replaces the last literal overlay list height, drawer handle width, modal default width, and tooltip arrow offset/radius with rem-based values so the overlay foundation can move into the protected shared-UI set cleanly. +- **Base + feedback primitive ratchet**: widened the shared reusable-UI ratchet into the next low-level component wave: `InputOTP`, fallback `RadioGroup`, fallback `ScrollArea`, fallback `Checkbox`, fallback `Switch`, base `table`, base `textarea`, `ErrorBoundary`, and `toast`. This pass removes the remaining pixel-bound focus-ring widths, checkbox alignment nudges, switch thumb offset, error-shell min-heights, and toast width cap so those shared primitives can ratchet upward without carrying raw px escapes. +- **Foundation-token control-surface ratchet**: widened the next shared reusable-UI ratchet to `IconButton`, fallback `Button`, base `input`, fallback `Select`, and `card`. This pass replaces the last protected-scope `foundation-size-*` control/header vars with their semantic scale equivalents (`11` and `14`), removes `Select`’s remaining `3px` focus ring, and moves the core control/card surfaces into the hard-error + touched-file ratchet path alongside the rest of the cleaned primitive layer. +- **Storybook holding-story ratchet**: widened the next bounded learning-surface ratchet to the `_holding` `Chart`, `IconButton`, `MessageActions`, and `NavigationMenu` stories. This pass removes their remaining foundation color vars and raw px story sizing in favor of semantic background/color vars and rem-based widths so these examples stop teaching the old token layer to agents and contributors. +- **Storybook holding-docs ratchet**: widened the next bounded documentation ratchet to the `_holding/docs` MDX set (`APIReference`, `DesignSystem`, `GettingStarted`, `Migration`, `Patterns`, `QuickStart`). This pass swaps foundation-token examples for semantic theme examples and moves the docs themselves into the hard-error + touched-file ratchet path so the guidance surfaces teach the same token discipline as product code. +- **PR2 settings recipe pass**: normalized the remaining settings-adjacent recipe surfaces so they teach the same composition language as the protected settings cluster. `AudioSettingsPanel` and `CheckForUpdatesPanel` now use `SettingsPanelState` for neutral empty-state scaffolding instead of raw placeholder text, `PersonalizationPanel` now uses consistent section rhythm/labels around the existing `SettingRow` / `SettingToggle` / `SettingDropdown` recipe parts, and the shared setting primitives now use cleaner `cn(...)` composition plus better focus/description wiring. +- **Template widget exemplar route**: added `/template-widget/` to the runtime router so `TemplateWidgetPage` is no longer build-only for verification, committed a Chromium baseline for `/template-widget/educational-starter`, and widened `scripts/policy/run-exemplar-evaluation.mjs` from a single template-browser web check to a template-family web gate. +- **Storybook exemplar timeout headroom**: bumped `platforms/web/apps/storybook/playwright.visual.config.ts` from a 60-second to a 90-second test timeout after the protected dark-theme `ManageAppsPanel` states started timing out late in the sequential exemplar sweep. The targeted rerun for those loading/empty states now completes cleanly under the same gate path. +- **PR1 protected-scope expansion**: widened the hard-error settings wave to cover the remaining settings Storybook stories plus `platforms/web/apps/web/src/pages/TemplateWidgetPage.tsx`. The story wrappers now rely on semantic `bg-background` / token-backed text utilities instead of raw hex background presets and raw CSS-variable utility escapes, and `TemplateWidgetPage` now uses a semantic `min-h-dvh` shell with `bg-background`, `text-foreground`, and `text-muted-foreground` instead of foundation-token and raw-pixel fallbacks. `pnpm design-system-guidance:ratchet`, `pnpm test:policy`, and `git diff --check` all pass with the widened boundary. +- **Touched-file ratchet**: added `pnpm design-system-guidance:ratchet` and wired it into root policy as `Touched-file Ratchet`. The new `scripts/policy/run-guidance-ratchet.mjs` fails only when touched warn-scope files inside the first ratcheted surface set still carry guidance warnings. To support that, the guidance CLI now has a `--json` mode and flushes large JSON payloads before exiting, and `TagInput` dropped its raw `min-w-[120px]` utility in favor of `min-w-30` so the touched reusable-forms slice stays clean. +- **Exemplar update path**: `scripts/policy/run-exemplar-evaluation.mjs` now forwards passthrough Playwright args, and the root scripts `pnpm test:exemplar-evaluation:list` / `pnpm test:exemplar-evaluation:update` are the canonical way to inspect or refresh the policy-owned exemplar slice. This replaces the fragile manual env-string approach that drifted onto stale `app-settings-*` story IDs and shell newline breakage. +- **Exemplar runner alignment**: the exemplar runner now mirrors the current test contract exactly. It forwards Playwright passthrough args for the protected Storybook settings slice, strips pnpm's script-argument delimiter before invoking Playwright, and always includes the `TemplateBrowserPage` web visual now that the page-level baseline is committed. +- **Storybook overlay isolation**: visual regression URLs now pass `devOverlays=0` / `agentation=0`, and Storybook preview honors those flags before mounting Agentation or Dialkit. This keeps Playwright captures stable and prevents the annotation/MCP overlay from poisoning settings-panel snapshots. +- **Storybook visual stability**: `platforms/web/apps/storybook/playwright.visual.config.ts` now runs with `workers: 1`, `fullyParallel: false`, and a 60-second timeout. The visual gate had started writing correct settings snapshots and then timing out under local multi-worker pressure, so the config now prefers deterministic sequential capture. +- **Theme-correct Storybook baselines**: `platforms/web/apps/storybook/storybook-visual.spec.ts` now derives its base URL from the configured Storybook host/port and sets both `globals.theme` and `backgrounds.value` in screenshot URLs. This fixes the earlier regression where `*-light.png` captures still rendered the dark theme on a light canvas. +- **Storybook watch hardening**: `platforms/web/apps/storybook/.storybook/main.ts` now ignores `playwright-report`, `test-results`, and `__snapshots__` in Vite watch mode. The exemplar capture path was dropping the dev server mid-run because Storybook could see Playwright mutating its own artifact directories while snapshots and reports were being written. +- **Template browser always-on baseline**: `platforms/web/apps/web/tests/visual/pages.visual.spec.ts` now checks `TemplateBrowserPage` unconditionally, the corresponding snapshot has been captured, and `scripts/policy/run-exemplar-evaluation.mjs` now runs that page visual on every exemplar-policy pass instead of behind an opt-in flag. +- **Artifact tracking cleanup**: `.gitignore` now explicitly ignores local Playwright HTML reports, nested Vite cache outputs, nested `.tmp` scratch files, and `reports/audit-*` artifacts. The intended committed outputs are still the Chromium baselines under `tests/visual/__snapshots__`, so UI test runs can stay reviewable without spraying cache/report churn into git status. +- **Visual suite preflight hardening**: `scripts/run-playwright-suite.mjs` now checks for the required Playwright Chromium binary before non-`--list` runs and fails fast with `pnpm exec playwright install chromium` as the remediation. This keeps the browser-backed exemplar and visual commands readable on machines that have the package but not the browser download. +- **Visual-path stabilization**: added `packages/ui build:visual` plus `scripts/run-playwright-suite.mjs`, then moved the root `test:visual:web`, `test:visual:storybook`, `test:e2e:web`, and widget a11y prebuild path onto that visual-only build. This keeps Playwright arg forwarding clean and removes the earlier declaration-generation noise from visual-only runs. +- **Review fixes + next exemplar pass**: made the template-browser web visual opt-in behind `PLAYWRIGHT_INCLUDE_TEMPLATE_BROWSER_VISUAL` until baselines are committed, replaced the protected settings loading shell's raw pixel min-height with `min-h-60`, and swapped neutral app badges away from `bg-foreground` so GitHub/Notion/Sora/Terminal keep contrast in dark theme. I also expanded `scripts/policy/run-exemplar-evaluation.mjs` so the protected Storybook exemplar set now includes explicit loading, empty, error, and busy state stories instead of only the default variants. +- **Protected settings state exemplars**: added shared async-state coverage to the protected settings slice. `AppsPanel`, `ManageAppsPanel`, `NotificationsPanel`, `SecurityPanel`, and `DataControlsPanel` now expose explicit loading, error, empty, or busy seams where relevant; the five settings story files now include state variants; and `packages/ui/src/app/settings/settings-panels.exemplar.test.tsx` locks the slice with jsdom checks for loading/empty/error plus keyboard and busy-state behavior. +- **Exemplar evaluation wiring**: added a dedicated `pnpm test:exemplar-evaluation` runner for the protected settings exemplar visuals plus `TemplateBrowserPage`, with Storybook visual tests filterable through env-driven story ID lists. This gate now belongs in browser-aware CI after Playwright install rather than inside the browser-free `pnpm test:policy` contract. +- **Policy/CI ordering fix**: removed the Playwright-backed exemplar subcontract from `scripts/policy/run.mjs` so `pnpm test:policy` stays safe for pre-Playwright CI and release workflows, and added a dedicated `Exemplar evaluation (web platform only)` step in `.github/workflows/ci.yml` immediately after Playwright installation. That preserves the browser-backed guardrail without breaking the repo's existing workflow ordering. +- **Review follow-up fixes**: expired entries in `docs/design-system/ENFORCEMENT_EXEMPTIONS.json` no longer suppress the underlying guidance violation once `removeBy` has passed, so CI now reports both the stale-ledger problem and the real rule failure. `scripts/codex-preflight.sh` also now uses the configured `rest_api.host` for all observe smoke probes instead of hard-coding `127.0.0.1`. +- **Root design-system hardening**: landed the first execution wave of the March 2026 agent-UI plan. Token truth now uses the vendored DTCG schema path, `fontDisplay` resolves from the correct top-level alias, coverage artifacts are regenerated and freshness-checked, and `scripts/policy/run.mjs` now runs the repo-wide guidance contract rather than the package-local self-check. +- **Professional UI contract + routing docs**: added `docs/design-system/PROFESSIONAL_UI_CONTRACT.md`, `docs/design-system/AGENT_UI_ROUTING.md`, `docs/design-system/COMPONENT_LIFECYCLE.json`, and `docs/design-system/ENFORCEMENT_EXEMPTIONS.json`, and linked them from `docs/design-system/CONTRACT.md`. This is the new handoff grammar for agents and reviewers. +- **Protected settings migration**: migrated the first protected settings surfaces onto a shared `SettingsPanelShell` recipe and semantic typography/focus patterns. The main touched files are `AppsPanel`, `ManageAppsPanel`, `PersonalizationPanel`, `ArchivedChatsPanel`, `AudioSettingsPanel`, `CheckForUpdatesPanel`, `NotificationsPanel`, `SecurityPanel`, `DataControlsPanel`, `SettingToggle`, `SettingDropdown`, and `TemplateBrowserPage`. +- **Guidance scope rollout**: the root `.design-system-guidance.json` now uses an explicit product-surface allowlist for the protected settings wave plus `TemplateBrowserPage`, while broader app, storybook, and showcase surfaces stay in `warn`. This keeps the policy credible without pretending the rest of the repo is already clean. +- **Agent-facing design-system audit**: added `docs/audits/AGENT_UI_MARCH_2026_AUDIT.md` and indexed it in `docs/audits/README.md`. The audit captures March 2026 gaps that affect whether agents can consistently produce professional UI, including broken token validation, stale coverage checks, semantic-token bypasses, thin guidance rules, and missing component-routing guardrails. + +### 2026-03-22 + +- **Gold standard uplift** (commit `db75f06a`): pinned all GitHub Actions to full commit SHAs in `ci.yml`; added `tsconfig.strict.json` with `noUncheckedIndexedAccess` + `exactOptionalPropertyTypes` and an informational CI step tracking migration progress. +- **New components**: `Spinner` (a11y-ready, motion-reduce), `Stack`/`Flex`/`Grid` layout primitives (polymorphic `as`, typed gap/cols/align props), `DataTable` (client-side + server-side sort/pagination), `FileUpload` (drag-and-drop with size/type validation). All have test coverage. +- **ThemeProvider**: extended `Theme` type to include `high-contrast` and `system-high-contrast`; wired `prefers-contrast: more` media query; exported `EffectiveTheme` type from tokens index. +- **TypeDoc**: added `typedoc.json` + `docs:generate` scripts to `packages/tokens` and `packages/runtime`. + ### 2026-03-15 - **Storybook Gold Standard Audit**: Upgraded Storybook configurations and tests. Migrated theme decorator to Storybook `globals:` API to use the toolbar switcher. Added comprehensive `@storybook/test` `play()` interaction testing to 24 critical components (forms, overlays, dropdowns). Configured `@storybook/addon-coverage` explicitly and set up an automated token drift warning script hook (`scripts/check-token-drift.mjs`). @@ -132,6 +221,14 @@ See also: `~/.codex/instructions/Learnings.md` - Re-enabled the `risk-policy-gate` job in `.github/workflows/pr-pipeline.yml`, corrected its docs baseline step to `node scripts/check-doc-links.mjs`, and replaced the external coding-harness gate invocations with a repo-local helper at `scripts/harness_pr_pipeline.py`. This kept PR policy enforcement local to the repo instead of relying on unavailable private tooling. +### 2026-03-24 + +- Fixed the current PR #131 web accessibility regression by raising the contrast of small descriptive copy in `TemplateBrowserPage` and the harness modal. The failing E2E Axe scans were both tripping on low-contrast small text after the design-system hardening pass, so those helper lines now use the stronger secondary text token instead of `text-muted-foreground`. +- Fixed PR #131 CI version-sync drift by aligning `packages/ui`, `packages/widgets`, and `packages/cloudflare-template` back to the root workspace version `0.0.1`. This unblocks `pnpm sync:versions:check` in the build lanes and keeps the agent-UI hardening branch mergeable. +- Fixed the token-regeneration truth bug behind PR #131's new build failure. `packages/tokens/scripts/sync-dtcg.ts` now preserves the semantic `elevation` shadow scale and `ElevationToken` type when it regenerates `src/shadows.ts`, so the build pipeline's `pnpm generate:tokens` step no longer rewrites the token package into a shape that breaks `packages/tokens/src/index.ts`. +- Stabilized Storybook exemplar capture for PR #131 by pinning Playwright Storybook visuals to `en-US` / `UTC` and forcing fullscreen chat/settings stories onto the exact `1280x720` viewport inside `storybook-visual.spec.ts`. This is aimed at the Ubuntu-only drift seen in the `Exemplar evaluation (web platform only)` lane, where fullscreen Storybook screenshots diverged from the committed macOS-recorded baselines. +- Moved the always-on `pnpm test:exemplar-evaluation` CI step from the Ubuntu web build to the macOS build lane. That keeps the exemplar gate aligned with the currently committed macOS-recorded Storybook baselines while leaving the Ubuntu lane focused on policy/build coverage and downstream web-only jobs. + ### 2026-03-05 - Added the onboarding command center, task-first onboarding routes, and automated onboarding parity checks. This established a single authoritative onboarding path and automated drift detection for docs and setup guidance. @@ -142,7 +239,7 @@ project: design-system repo: ~/dev/design-system status: IN_PROGRESS health: yellow -last_updated: 2026-03-11 +last_updated: 2026-03-24 open_prs: 0 blockers: none next_milestone: ChatGPT widget integration diff --git a/docs/audits/AGENT_UI_MARCH_2026_AUDIT.md b/docs/audits/AGENT_UI_MARCH_2026_AUDIT.md new file mode 100644 index 00000000..b30e9599 --- /dev/null +++ b/docs/audits/AGENT_UI_MARCH_2026_AUDIT.md @@ -0,0 +1,161 @@ +# Agent-Facing Design System Audit For Professional UI (March 2026) + +Last updated: 2026-03-23 + +## Doc requirements + +- Audience: Developers (intermediate) +- Scope: Agent-facing design-system gaps that affect professional UI output quality +- Non-scope: One-off component bug fixes or visual redesign mockups +- Owner: TBD (confirm) +- Review cadence: Monthly while agent-facing guidance is still changing + +## Contents + +- [Doc requirements](#doc-requirements) +- [Executive summary](#executive-summary) +- [Audit lenses used](#audit-lenses-used) +- [Evidence snapshot](#evidence-snapshot) +- [Priority findings](#priority-findings) +- [Recommended roadmap](#recommended-roadmap) +- [Validation runbook](#validation-runbook) + +## Executive summary + +The design system has solid raw ingredients for professional UI output, including a token stack, semantic theme slots, Apps SDK integration, accessibility contracts, motion utilities, and a dedicated `design-system-guidance` package. The main gap is not a lack of primitives. The main gap is that the repo does not yet enforce the professional composition rules that agents need in order to reliably turn those primitives into polished product UI. + +As of March 2026, the highest-risk issue is that core design-system integrity checks are failing while broader policy checks still pass. That leaves both humans and agents with a false sense of safety. The next biggest gap is that the agent-facing guidance layer is still mostly limited to token hygiene, while high-signal professional UI rules such as semantic slot discipline, state completeness, focus behavior, layout quality, motion restraint, and component-preference routing are not yet encoded strongly enough to steer generation. + +## Audit lenses used + +This audit used the relevant frontend skills as review lenses: + +- `design-system` +- `baseline-ui` +- `frontend-ui-design` +- `fixing-accessibility` +- `react-ui-patterns` +- `ui-ux-creative-coding` +- `ui-visual-regression` +- `shadcn-ui` +- `better-icons` +- `test-browser` + +Generation-only frontend skills such as favicon, OG image, video, Stitch, and image-generation workflows were intentionally not used because they do not improve evidence quality for a design-system audit. + +## Evidence snapshot + +- `pnpm validate:tokens` failed with `SCHEMA_REFERENCE_INVALID` and `TOKEN_ALIAS_MISSING`. +- `pnpm ds:matrix:check` failed because the coverage matrix outputs are stale. +- `pnpm design-system-guidance:check:ci` passed, but it only validated the guidance package itself rather than the full monorepo. +- `pnpm test:policy` passed, which confirms the repo currently allows policy-green states while design-system integrity is still broken. +- Non-story, non-test source currently contains `1546` matches for `foundation-*`, which indicates widespread low-level token usage in product code. +- Non-story, non-test source currently contains `53` likely raw color literals, `582` arbitrary layout or typography utilities, and `189` outline-suppression patterns. + +## Priority findings + +### 1. Root validation is misaligned with real design-system integrity + +The repo's strongest design-system truth checks are currently broken, but the broader policy surface still reports green. `packages/tokens/scripts/validate-tokens.ts` expects a pinned local schema reference, while `packages/tokens/src/tokens/index.dtcg.json` points at the remote DTCG 2025.10 schema URL. The same validation run also reports that `type.fontDisplay` resolves to a missing `type.web.fontDisplay` path because `packages/tokens/src/alias-map.ts` generates `type.web.${key}` for typography aliases that do not actually exist in the source tree. + +Why this matters for agents: if the design system cannot prove its own token truth, agent outputs will inherit silent drift and reviewers will have to catch issues manually. + +### 2. The guidance layer is still too thin for "professional March 2026 UI" + +`packages/design-system-guidance/src/core.ts` currently enforces only a small set of checks: deprecated icon imports, raw hex colors, raw pixel values, and a minimal docs presence contract. That is helpful hygiene, but it does not encode the things that make an agent-produced interface feel professional. + +Missing guidance categories include: + +- semantic slot usage instead of direct `foundation-*` styling +- page-shell and surface composition rules +- state completeness for loading, empty, error, permission, and success paths +- icon-only button labeling and keyboard expectations +- `h-screen` to `dvh` guidance +- arbitrary tracking and type-rhythm restrictions +- motion ceilings and reduced-motion parity +- component-source precedence for Apps SDK, local primitives, and fallbacks + +### 3. Semantic token architecture is being bypassed in app surfaces + +`packages/ui/src/styles/theme.css` says mapped slots should only reference semantic alias variables, but the same file also re-exposes many foundation tokens for backward compatibility. That keeps a low-level escape hatch permanently available to agents and application code. + +The bypass is already visible in product surfaces: + +- `platforms/web/apps/web/src/pages/HarnessPage.tsx` +- `platforms/web/apps/web/src/pages/TemplateBrowserPage.tsx` + +Both pages style directly against `foundation-*` tokens instead of routing through semantic surface and content roles. This makes the repo easier to "make look tokenized" than to make feel coherent. + +### 4. Product components still contain raw brand literals and arbitrary typography + +`packages/ui/src/app/settings/AppsPanel/AppsPanel.tsx` and `packages/ui/src/app/settings/ManageAppsPanel/ManageAppsPanel.tsx` still encode brand palettes as raw hex values and use many arbitrary text, leading, and tracking utilities. `AppsPanel.tsx` also contains dead contrast logic where both branches resolve to `text-foreground`, which means the code suggests adaptive behavior that it does not actually perform. + +Why this matters for agents: generated UI tends to copy the examples it sees. If the system's own components rely on local literals and arbitrary utility escapes, agents will reproduce the same patterns. + +### 5. Focus guidance is internally contradictory + +`packages/tokens/src/enhanced.css` applies a bare global `:focus-visible` rule and removes the default outline, while `packages/tokens/src/enhanced/focus.ts` explicitly warns against using bare global `:focus-visible` because it can create double-ring conflicts and recommends scoped strategies such as `.ds-focusable`. + +This contradiction makes it harder for agents to know which focus pattern is canonical and increases the risk of inconsistent accessibility behavior. + +### 6. Apps SDK-first strategy is not yet operationalized enough for agent choice + +The charter and coverage docs establish Apps SDK UI as the canonical surface, but the repo does not yet expose a concise, agent-oriented "component routing contract" that answers questions such as: + +- When should an agent prefer an Apps SDK primitive? +- When is a local wrapper acceptable? +- When is a Radix fallback acceptable? +- What product states must every chosen primitive support? + +Without that routing layer, agents still have to infer intent from scattered docs and existing implementation examples. + +### 7. Motion, layout taste, and state quality exist as primitives but not as guardrails + +The repo already has enhanced motion tokens, patterns, and accessibility contracts. What is missing is a clear, enforceable standard for restrained professional motion, spacing rhythm, hierarchy, and page-level composition. This is the gap between "a system with tokens" and "a system that consistently produces polished software." + +## Recommended roadmap + +### P0. Repair trust in the system's own contracts + +- Fix the token validation mismatch between the expected pinned schema and the current remote schema reference. +- Fix the missing `fontDisplay` alias mapping in `packages/tokens/src/alias-map.ts`. +- Regenerate and re-check the design-system coverage matrix. +- Make token validation and matrix freshness part of the same root policy path that contributors already trust. + +### P1. Expand guidance from hygiene rules to professional UI rules + +- Extend `design-system-guidance` with checks for semantic-slot discipline, state completeness, focus behavior, accessibility labeling, arbitrary typography escapes, and viewport-safe layout rules. +- Add a repo-level configuration that runs these checks against product code, not just the guidance package. +- Add a short, agent-friendly "professional UI contract" document that defines non-negotiable output standards. + +### P1.5. Add an agent-native composition layer + +- Introduce page-shell, section, panel, and data-view primitives that encode hierarchy and spacing taste directly. +- Introduce semantic surface roles such as `canvas`, `panel`, `elevated`, `muted`, `accent`, and `danger`. +- Introduce typography presets for display, title, section label, metadata, and dense data rows so agents do not need to invent utility mixes. + +### P2. Reduce semantic bypasses in product code + +- Migrate exemplar app surfaces away from direct `foundation-*` usage and onto mapped semantic roles. +- Replace hard-coded app brand colors with a branded-but-safe token strategy or a documented exception path. +- Narrow or remove backward-compatibility foundation re-exports once migration coverage is acceptable. + +### P3. Add review loops for professional finish + +- Pair policy checks with visual regression rules that flag hierarchy collapse, spacing drift, and focus-ring regressions on exemplar surfaces. +- Add a design review rubric for "professional by default" output quality that agents and reviewers can use consistently. +- Add a small set of gold-standard reference screens that agents should emulate structurally, not copy literally. + +## Validation runbook + +Run these commands when landing design-system improvements from this audit: + +```bash +pnpm validate:tokens +pnpm ds:matrix:generate +pnpm ds:matrix:check +pnpm design-system-guidance:check:ci +pnpm test:policy +``` + +When the guidance layer expands beyond package-local checks, add a repo-level command that validates representative app surfaces against the same contracts. diff --git a/docs/audits/README.md b/docs/audits/README.md index aedda320..fd47e20c 100644 --- a/docs/audits/README.md +++ b/docs/audits/README.md @@ -1,6 +1,6 @@ # Design System Audits -Last updated: 2026-01-04 +Last updated: 2026-03-23 ## Doc requirements @@ -14,6 +14,7 @@ Last updated: 2026-01-04 - [Doc requirements](#doc-requirements) - [Documents](#documents) + - [[AGENT_UI_MARCH_2026_AUDIT.md](./AGENT_UI_MARCH_2026_AUDIT.md)](#agentuimarch2026auditmdagentuimarch2026auditmd) - [[APPS_SDK_COMPLIANCE_AUDIT.md](./APPS_SDK_COMPLIANCE_AUDIT.md)](#appssdkcomplianceauditmdappssdkcomplianceauditmd) - [[COLOR_AUDIT_COMPLETE.md](./COLOR_AUDIT_COMPLETE.md)](#colorauditcompletemdcolorauditcompletemd) - [[COLOR_REFERENCE.md](./COLOR_REFERENCE.md)](#colorreferencemdcolorreferencemd) @@ -25,6 +26,17 @@ Comprehensive audits of design system compliance and implementation. ## Documents +### [AGENT_UI_MARCH_2026_AUDIT.md](./AGENT_UI_MARCH_2026_AUDIT.md) + +March 2026 audit of the design system as an agent-facing UI production system. + +**Topics:** + +- design-system integrity gaps +- semantic token discipline +- professional UI guidance for agents +- prioritized improvement roadmap + ### [APPS_SDK_COMPLIANCE_AUDIT.md](./APPS_SDK_COMPLIANCE_AUDIT.md) Audit of Apps SDK UI compliance across the codebase. diff --git a/docs/design-system/AGENT_UI_ROUTING.md b/docs/design-system/AGENT_UI_ROUTING.md new file mode 100644 index 00000000..3f89175c --- /dev/null +++ b/docs/design-system/AGENT_UI_ROUTING.md @@ -0,0 +1,66 @@ +# Agent UI Routing + +Last updated: 2026-03-23 +Owner: Jamie Scott Craik (@jscraik) +Review cadence: Every release or monthly (whichever is sooner) + +## Table of Contents +- [Purpose](#purpose) +- [Routing order](#routing-order) +- [Lifecycle source of truth](#lifecycle-source-of-truth) +- [Settings cluster defaults](#settings-cluster-defaults) +- [Do not create these by default](#do-not-create-these-by-default) + +## Purpose + +This guide tells agents and developers which component layer to reach for first when building or refactoring product UI in this repository. + +It should reduce unnecessary wrapper creation and keep routing decisions consistent with the design-system contract. + +## Routing order + +Use this order unless a documented exception exists: + +1. Apps SDK primitive when available and sufficient +2. Local design-system primitive or composition when product semantics are needed +3. Fallback primitive only for documented gaps + +Before creating a new abstraction, check: + +- `docs/design-system/COVERAGE_MATRIX.json` +- `docs/design-system/COMPONENT_LIFECYCLE.json` +- `docs/design-system/PROFESSIONAL_UI_CONTRACT.md` + +## Lifecycle source of truth + +Lifecycle classification lives in `docs/design-system/COMPONENT_LIFECYCLE.json`. + +Use the manifest to decide whether a component is: + +- `canonical` +- `transitional` +- `deprecated` + +If a component is not in the manifest, do not assume it is a preferred routing target. + +## Settings cluster defaults + +For the settings cluster, prefer recipe-first normalization over new public exports. + +Current defaults are: + +- `Stack` for vertical rhythm +- `Flex` for row alignment +- `SectionHeader` for repeated section heading structure +- `CollapsibleSection` for advanced or expandable sections +- `EmptyMessage`, `Spinner`, and `ErrorBoundary` for state handling +- `SettingRow`, `SettingToggle`, and `SettingDropdown` as transitional internal recipes while the cluster is normalized + +## Do not create these by default + +Avoid introducing: + +- a second settings shell primitive parallel to existing layout primitives +- a settings-specific layout primitive that duplicates `Stack` or `Flex` +- one-off row exports such as `NotificationRow` or `DangerRow` +- settings-only empty, loading, or error components that duplicate shared primitives diff --git a/docs/design-system/COMPONENT_LIFECYCLE.json b/docs/design-system/COMPONENT_LIFECYCLE.json new file mode 100644 index 00000000..3ff0742a --- /dev/null +++ b/docs/design-system/COMPONENT_LIFECYCLE.json @@ -0,0 +1,97 @@ +{ + "schema_version": 1, + "updated_at": "2026-03-23", + "components": [ + { + "name": "Stack", + "path": "packages/ui/src/components/ui/layout/Stack/Stack.tsx", + "lifecycle": "canonical", + "routing_tier": 2, + "notes": "Primary vertical composition primitive for product surfaces." + }, + { + "name": "Flex", + "path": "packages/ui/src/components/ui/layout/Flex/Flex.tsx", + "lifecycle": "canonical", + "routing_tier": 2, + "notes": "Primary row and inline alignment primitive for product surfaces." + }, + { + "name": "Grid", + "path": "packages/ui/src/components/ui/layout/Grid/Grid.tsx", + "lifecycle": "canonical", + "routing_tier": 2, + "notes": "Use when a real grid is needed; do not replace list-based settings layouts with Grid by default." + }, + { + "name": "SectionHeader", + "path": "packages/ui/src/components/ui/base/SectionHeader/SectionHeader.tsx", + "lifecycle": "canonical", + "routing_tier": 2, + "notes": "Preferred section heading primitive for grouped settings and product sections." + }, + { + "name": "CollapsibleSection", + "path": "packages/ui/src/components/ui/base/CollapsibleSection/CollapsibleSection.tsx", + "lifecycle": "canonical", + "routing_tier": 2, + "notes": "Preferred expandable section primitive when progressive disclosure is needed." + }, + { + "name": "EmptyMessage", + "path": "packages/ui/src/components/ui/data-display/EmptyMessage/EmptyMessage.tsx", + "lifecycle": "canonical", + "routing_tier": 2, + "notes": "Preferred neutral no-data state primitive." + }, + { + "name": "Spinner", + "path": "packages/ui/src/components/ui/feedback/Spinner/Spinner.tsx", + "lifecycle": "canonical", + "routing_tier": 2, + "notes": "Preferred loading-state primitive." + }, + { + "name": "ErrorBoundary", + "path": "packages/ui/src/components/ui/feedback/ErrorBoundary/ErrorBoundary.tsx", + "lifecycle": "canonical", + "routing_tier": 2, + "notes": "Preferred shared error boundary primitive." + }, + { + "name": "DataTable", + "path": "packages/ui/src/components/ui/data-display/DataTable/DataTable.tsx", + "lifecycle": "canonical", + "routing_tier": 2, + "notes": "Use for tabular data views; do not hand-roll table layouts when this fits." + }, + { + "name": "ChatShell", + "path": "packages/ui/src/app/chat/ChatShell/ChatShell.tsx", + "lifecycle": "canonical", + "routing_tier": 2, + "notes": "Preferred shell reference for chat-oriented surfaces." + }, + { + "name": "SettingRow", + "path": "packages/ui/src/app/settings/SettingRow/SettingRow.tsx", + "lifecycle": "transitional", + "routing_tier": 2, + "notes": "Existing settings row primitive that should anchor recipe-first normalization before any new exports are added." + }, + { + "name": "SettingToggle", + "path": "packages/ui/src/app/settings/SettingToggle/SettingToggle.tsx", + "lifecycle": "transitional", + "routing_tier": 2, + "notes": "Existing control-row primitive that should replace repeated hand-rolled toggle rows in settings surfaces." + }, + { + "name": "SettingDropdown", + "path": "packages/ui/src/app/settings/SettingDropdown/SettingDropdown.tsx", + "lifecycle": "transitional", + "routing_tier": 2, + "notes": "Existing settings selector primitive pending cluster-wide normalization." + } + ] +} diff --git a/docs/design-system/CONTRACT.md b/docs/design-system/CONTRACT.md index a874967e..545a765e 100644 --- a/docs/design-system/CONTRACT.md +++ b/docs/design-system/CONTRACT.md @@ -6,9 +6,20 @@ Review cadence: Every release or monthly (whichever is sooner) This index links all governing artifacts for the UI design system. +## Table of Contents + +- [Governance](#governance) +- [Tokens](#tokens) +- [Coverage and Enforcement](#coverage-and-enforcement) +- [Export Naming Policy](#export-naming-policy) +- [QA Gates](#qa-gates) +- [Required Updates When Changing UI](#required-updates-when-changing-ui) + ## Governance - Charter: `docs/design-system/CHARTER.md` +- Professional UI contract: `docs/design-system/PROFESSIONAL_UI_CONTRACT.md` +- Agent routing guide: `docs/design-system/AGENT_UI_ROUTING.md` - Upstream alignment: `docs/design-system/UPSTREAM_ALIGNMENT.md` - RFC template: `docs/workflows/RFC_TEMPLATE.md` - Transcripts index (training/reference): `docs/transcripts/README.md` @@ -55,6 +66,8 @@ Evidence: docs/transcripts/rYzstFEY0t8.cleaned.md - Coverage matrix (JSON): `docs/design-system/COVERAGE_MATRIX.json` - Coverage matrix (MD): `docs/design-system/COVERAGE_MATRIX.md` - Coverage matrix surface usage: `docs/design-system/COVERAGE_MATRIX_SURFACES.json` +- Component lifecycle manifest: `docs/design-system/COMPONENT_LIFECYCLE.json` +- Enforcement exemptions ledger: `docs/design-system/ENFORCEMENT_EXEMPTIONS.json` - Matrix generator: `scripts/generate-coverage-matrix.ts` - Policy enforcement rules: `scripts/policy/run.mjs` - QA evidence schema: `docs/operations/QA_EVIDENCE_SCHEMA.md` diff --git a/docs/design-system/COVERAGE_MATRIX.json b/docs/design-system/COVERAGE_MATRIX.json index e4524a0f..9ece782d 100644 --- a/docs/design-system/COVERAGE_MATRIX.json +++ b/docs/design-system/COVERAGE_MATRIX.json @@ -311,6 +311,19 @@ "tauri_used": false, "widget_used": false }, + { + "name": "DataTable", + "source": "local_primitive", + "upstream": null, + "fallback": null, + "why_missing_upstream": null, + "migration_trigger": null, + "a11y_contract_ref": "docs/design-system/A11Y_CONTRACTS.md#datatable", + "status": "active", + "web_used": false, + "tauri_used": false, + "widget_used": false + }, { "name": "DatePicker", "source": "local_primitive", @@ -389,6 +402,32 @@ "tauri_used": false, "widget_used": false }, + { + "name": "FileUpload", + "source": "local_primitive", + "upstream": null, + "fallback": null, + "why_missing_upstream": null, + "migration_trigger": null, + "a11y_contract_ref": "docs/design-system/A11Y_CONTRACTS.md#fileupload", + "status": "active", + "web_used": false, + "tauri_used": false, + "widget_used": false + }, + { + "name": "Flex", + "source": "local_primitive", + "upstream": null, + "fallback": null, + "why_missing_upstream": null, + "migration_trigger": null, + "a11y_contract_ref": "docs/design-system/A11Y_CONTRACTS.md#flex", + "status": "active", + "web_used": false, + "tauri_used": false, + "widget_used": false + }, { "name": "Form", "source": "radix_fallback", @@ -402,6 +441,19 @@ "tauri_used": false, "widget_used": false }, + { + "name": "Grid", + "source": "local_primitive", + "upstream": null, + "fallback": null, + "why_missing_upstream": null, + "migration_trigger": null, + "a11y_contract_ref": "docs/design-system/A11Y_CONTRACTS.md#grid", + "status": "active", + "web_used": false, + "tauri_used": false, + "widget_used": false + }, { "name": "HoverCard", "source": "radix_fallback", @@ -766,6 +818,32 @@ "tauri_used": false, "widget_used": false }, + { + "name": "Spinner", + "source": "local_primitive", + "upstream": null, + "fallback": null, + "why_missing_upstream": null, + "migration_trigger": null, + "a11y_contract_ref": "docs/design-system/A11Y_CONTRACTS.md#spinner", + "status": "active", + "web_used": false, + "tauri_used": false, + "widget_used": false + }, + { + "name": "Stack", + "source": "local_primitive", + "upstream": null, + "fallback": null, + "why_missing_upstream": null, + "migration_trigger": null, + "a11y_contract_ref": "docs/design-system/A11Y_CONTRACTS.md#stack", + "status": "active", + "web_used": false, + "tauri_used": false, + "widget_used": false + }, { "name": "Switch", "source": "radix_fallback", diff --git a/docs/design-system/COVERAGE_MATRIX.md b/docs/design-system/COVERAGE_MATRIX.md index 017c070d..5d3012a5 100644 --- a/docs/design-system/COVERAGE_MATRIX.md +++ b/docs/design-system/COVERAGE_MATRIX.md @@ -27,13 +27,17 @@ Upstream version: @openai/apps-sdk-ui ^0.2.1 | CollapsibleSection | local_primitive | - | - | - | - | docs/design-system/A11Y_CONTRACTS.md#collapsiblesection | active | no | no | no | | ContextMenu | radix_fallback | - | radix | Apps SDK UI lacks this component or required API parity. | Replace with Apps SDK UI component when available with matching props and behavior. | docs/KEYBOARD_NAVIGATION_TESTS.md | active | no | no | no | | ContextTag | local_primitive | - | - | - | - | docs/design-system/A11Y_CONTRACTS.md#contexttag | active | no | no | no | +| DataTable | local_primitive | - | - | - | - | docs/design-system/A11Y_CONTRACTS.md#datatable | active | no | no | no | | DatePicker | local_primitive | - | - | - | - | docs/design-system/A11Y_CONTRACTS.md#datepicker | active | no | no | no | | Dialog | radix_fallback | - | radix | Apps SDK UI lacks this component or required API parity. | Replace with Apps SDK UI component when available with matching props and behavior. | docs/KEYBOARD_NAVIGATION_TESTS.md | active | no | no | no | | DirectionProvider | radix_fallback | - | radix | Apps SDK UI lacks this component or required API parity. | Replace with Apps SDK UI component when available with matching props and behavior. | docs/KEYBOARD_NAVIGATION_TESTS.md | active | no | no | no | | DropdownMenu | radix_fallback | - | radix | Apps SDK UI lacks this component or required API parity. | Replace with Apps SDK UI component when available with matching props and behavior. | docs/KEYBOARD_NAVIGATION_TESTS.md | active | no | no | no | | EmptyMessage | local_primitive | - | - | - | - | docs/design-system/A11Y_CONTRACTS.md#emptymessage | active | no | no | no | | ErrorBoundary | local_primitive | - | - | - | - | docs/design-system/A11Y_CONTRACTS.md#errorboundary | active | no | no | no | +| FileUpload | local_primitive | - | - | - | - | docs/design-system/A11Y_CONTRACTS.md#fileupload | active | no | no | no | +| Flex | local_primitive | - | - | - | - | docs/design-system/A11Y_CONTRACTS.md#flex | active | no | no | no | | Form | radix_fallback | - | radix | Apps SDK UI lacks this component or required API parity. | Replace with Apps SDK UI component when available with matching props and behavior. | docs/KEYBOARD_NAVIGATION_TESTS.md | active | no | no | no | +| Grid | local_primitive | - | - | - | - | docs/design-system/A11Y_CONTRACTS.md#grid | active | no | no | no | | HoverCard | radix_fallback | - | radix | Apps SDK UI lacks this component or required API parity. | Replace with Apps SDK UI component when available with matching props and behavior. | docs/KEYBOARD_NAVIGATION_TESTS.md | active | no | no | no | | IconButton | local_primitive | - | - | - | - | docs/design-system/A11Y_CONTRACTS.md#iconbutton | active | no | no | no | | Image | local_primitive | - | - | - | - | docs/design-system/A11Y_CONTRACTS.md#image | active | no | no | no | @@ -62,6 +66,8 @@ Upstream version: @openai/apps-sdk-ui ^0.2.1 | ShimmerText | local_primitive | - | - | - | - | docs/design-system/A11Y_CONTRACTS.md#shimmertext | active | no | no | no | | Sidebar | radix_fallback | - | radix | Apps SDK UI lacks this component or required API parity. | Replace with Apps SDK UI component when available with matching props and behavior. | docs/KEYBOARD_NAVIGATION_TESTS.md | active | no | no | no | | Slider | radix_fallback | - | radix | Apps SDK UI lacks this component or required API parity. | Replace with Apps SDK UI component when available with matching props and behavior. | docs/KEYBOARD_NAVIGATION_TESTS.md | active | no | no | no | +| Spinner | local_primitive | - | - | - | - | docs/design-system/A11Y_CONTRACTS.md#spinner | active | no | no | no | +| Stack | local_primitive | - | - | - | - | docs/design-system/A11Y_CONTRACTS.md#stack | active | no | no | no | | Switch | radix_fallback | - | radix | Apps SDK UI lacks this component or required API parity. | Replace with Apps SDK UI component when available with matching props and behavior. | docs/KEYBOARD_NAVIGATION_TESTS.md | active | no | no | no | | Tabs | radix_fallback | - | radix | Apps SDK UI lacks this component or required API parity. | Replace with Apps SDK UI component when available with matching props and behavior. | docs/KEYBOARD_NAVIGATION_TESTS.md | active | no | no | no | | TagInput | local_primitive | - | - | - | - | docs/design-system/A11Y_CONTRACTS.md#taginput | active | no | no | no | diff --git a/docs/design-system/ENFORCEMENT_EXEMPTIONS.json b/docs/design-system/ENFORCEMENT_EXEMPTIONS.json new file mode 100644 index 00000000..57c38aef --- /dev/null +++ b/docs/design-system/ENFORCEMENT_EXEMPTIONS.json @@ -0,0 +1,5 @@ +{ + "schema_version": 1, + "updated_at": "2026-03-23", + "exemptions": [] +} diff --git a/docs/design-system/PROFESSIONAL_UI_CONTRACT.md b/docs/design-system/PROFESSIONAL_UI_CONTRACT.md new file mode 100644 index 00000000..14975943 --- /dev/null +++ b/docs/design-system/PROFESSIONAL_UI_CONTRACT.md @@ -0,0 +1,169 @@ +# Professional UI Contract + +Last updated: 2026-03-23 +Owner: Jamie Scott Craik (@jscraik) +Review cadence: Every release or monthly (whichever is sooner) + +## Table of Contents +- [Purpose](#purpose) +- [Protected Surfaces](#protected-surfaces) +- [Hierarchy Grammar](#hierarchy-grammar) +- [Surface Roles](#surface-roles) +- [State Model](#state-model) +- [State Presentation Matrix](#state-presentation-matrix) +- [Focus Contract](#focus-contract) +- [Motion Contract](#motion-contract) +- [Viewport And Input Defaults](#viewport-and-input-defaults) +- [Enforcement Inputs](#enforcement-inputs) + +## Purpose + +This document is the normative contract for agent-produced professional UI in this repository. + +It exists to reduce invention at the component call-site. Agents and developers should be able to answer: + +- which surfaces are under the strictest rules +- which hierarchy and state patterns are required +- which defaults are acceptable for focus, motion, and layout +- which source of truth to consult before creating a new abstraction + +## Protected Surfaces + +Day 1 protected surfaces are: + +- `packages/ui/src/app/settings/**/*.{ts,tsx,js,jsx,css}` +- `platforms/web/apps/web/src/pages/TemplateBrowserPage.tsx` + +Protected surfaces must: + +- use semantic and mapped tokens instead of `foundation-*` variables +- avoid literal hex colors and arbitrary `px` values except where explicitly ledgered +- avoid `h-screen` and other viewport shortcuts that ignore dynamic viewport units +- follow the state, focus, and motion rules in this contract + +Non-protected reusable UI and showcase surfaces may start as warnings, but they should still move toward this contract. + +## Hierarchy Grammar + +Use one clear reading order per surface: + +1. page or panel title +2. short orienting support copy when needed +3. grouped sections with consistent spacing rhythm +4. rows, controls, or data views inside those sections +5. low-emphasis metadata and destructive affordances last + +Hierarchy defaults: + +- One primary title per page or panel. +- Section titles should introduce a meaningful chunk of settings or content, not a single row. +- Support copy should explain consequence or context, not restate the label. +- Dense settings rows should prefer title plus optional helper copy over free-form mixed typography. +- Metadata belongs in muted secondary styles, not in the same emphasis tier as the primary action. +- CTA emphasis should be singular. A surface may have one obvious primary action; all others should step down. + +Typography and spacing choices should come from semantic presets or existing primitives first. Protected surfaces must not depend on arbitrary `tracking-[...]`, `text-[...]`, or one-off spacing values when the design system already has a matching role. + +## Surface Roles + +Surface roles should be assigned intentionally: + +- `shell`: owns viewport, safe-area, and major section layout +- `section`: owns grouping, section heading, and section spacing +- `row`: owns label, helper copy, and trailing affordance composition +- `state`: owns loading, empty, error, permission, and busy chrome +- `accent`: owns emphasis, never the overall layout vocabulary + +Token discipline: + +- Brand values live in DTCG and generated foundations. +- Alias values express semantic intent. +- Mapped theme slots are the only values product surfaces should consume directly. +- Direct `foundation-*` usage is reserved for token/theme internals and explicit compatibility layers. + +## State Model + +Every async exemplar surface must define: + +- `ready` +- `loading` +- `error` +- one neutral no-data state: `empty` or `first-run` + +Add the following only when relevant: + +- `permission-denied` +- `busy` +- `partial-failure` +- `confirm-destructive` + +State quality rules: + +- Loading must preserve context when possible. +- Empty states should explain what the user can do next. +- Error states should separate cause from recovery. +- Busy states should preserve layout stability and make the in-flight target obvious. + +## State Presentation Matrix + +Choose the lightest presentation that matches the severity: + +- Inline error: local validation or one-row/action failure. +- Banner or alert: page-level recoverable issue that still permits progress. +- Blocking dialog: destructive confirmation or hard interruption. +- Toast: transient non-blocking confirmation only. + +Do not use a toast for a blocking error, and do not use a blocking dialog for ordinary validation or recoverable fetch failure. + +## Focus Contract + +Scoped opt-in focus selectors are canonical. + +Rules: + +- Shared CSS must not apply a bare global `:focus-visible` ring to every element. +- Focus affordances should come from a scoped class or data attribute pattern. +- Unscoped elements should keep safe native behavior instead of receiving design-system-imposed rings. +- Keyboard focus must be visually distinct with one ring, not stacked or doubled. + +## Motion Contract + +Professional product surfaces should feel responsive, not decorative. + +Rules: + +- Animate compositor-friendly properties only. +- Small feedback caps at `160ms`. +- Enter and replace transitions cap at `220ms`. +- Expand and collapse transitions cap at `280ms`. +- Dense and productivity-oriented surfaces do not get decorative looping motion. +- Reduced motion must fall back to opacity-only or no motion. + +## Viewport And Input Defaults + +Layout defaults: + +- Protected surfaces must not use `h-screen`. +- Use `100dvh`-safe shells for full-height panels and pages. +- Sticky and fixed UI must respect safe-area insets. + +Input defaults: + +- Minimum touch target is `44x44`. +- Hover-only affordances must have a touch or click path. +- Icon-only controls must expose accessible names. + +## Enforcement Inputs + +The contract is enforced through: + +- `docs/design-system/COMPONENT_LIFECYCLE.json` +- `docs/design-system/ENFORCEMENT_EXEMPTIONS.json` +- `docs/design-system/COVERAGE_MATRIX.json` +- repo-level design-system-guidance config +- `pnpm validate:tokens` +- `pnpm ds:matrix:check` +- `pnpm design-system-guidance:check:ci` +- `pnpm test:policy` + +If this contract and a local surface disagree, the protected-surface rules in this document win unless a time-bounded exemption is present in the enforcement ledger. diff --git a/docs/risks/APPS_SDK_VENDOR_RISK.md b/docs/risks/APPS_SDK_VENDOR_RISK.md new file mode 100644 index 00000000..58ad2e66 --- /dev/null +++ b/docs/risks/APPS_SDK_VENDOR_RISK.md @@ -0,0 +1,163 @@ +# Vendor Risk: @openai/apps-sdk-ui Dependency + +**Severity:** High +**Status:** Documented — mitigation plan in progress +**Owner:** Design System team +**Last reviewed:** 2026-03-22 + +--- + +## Table of Contents + +- [Summary](#summary) +- [Dependency surface](#dependency-surface) +- [Risk scenarios](#risk-scenarios) +- [Component ownership map](#component-ownership-map) +- [Mitigation strategy](#mitigation-strategy) +- [Contingency plan](#contingency-plan) +- [Monitoring](#monitoring) + +--- + +## Summary + +`@openai/apps-sdk-ui` is the visual and component foundation for this design system. +It provides base primitives (Button, Input, Card, Dialog, etc.) that our `packages/ui` +components build on. This creates a single-vendor dependency with no currently documented +escape hatch — any breaking change, deprecation, or access restriction in the upstream +package would require a full re-skin with no migration path defined. + +This document records the risk, maps the dependency surface, and defines a contingency plan. + +--- + +## Dependency surface + +``` +packages/ui +└── src/components/ui/ + ├── base/ ← wraps @openai/apps-sdk-ui primitives directly + │ ├── button.tsx + │ ├── input.tsx + │ ├── table.tsx + │ ├── card.tsx + │ ├── badge.tsx + │ └── … + ├── feedback/ ← our components, use base/ only + ├── layout/ ← our components, no SDK dep + ├── forms/ ← our components, use base/ only + └── data-display/ ← our components, use base/ only +``` + +**Direct SDK consumers in `base/`:** ~12 primitive wrappers +**Indirect consumers (our components):** ~18 components that import from `base/` +**External consumers (apps):** anything importing from `@design-studio/ui` + +--- + +## Risk scenarios + +| Scenario | Probability | Impact | Current exposure | +|---|---|---|---| +| Breaking API change in a minor/patch release | Medium | High | All 12 base wrappers need updates | +| Package renamed or deprecated | Low | Critical | Full re-skin required | +| License change restricting internal use | Low | Critical | Must fork or replace | +| OpenAI discontinues Apps SDK | Very low | Critical | No fallback | +| Upstream introduces a dependency we cannot use (size/security) | Low | Medium | Build pipeline impacted | + +--- + +## Component ownership map + +This table maps each base wrapper to its upstream SDK component and our abstraction depth. + +| Our component | SDK source | Abstraction depth | Escapable? | +|---|---|---|---| +| `