From 3401cc0d80c4a1dbbad244872040937bc8ddd8f8 Mon Sep 17 00:00:00 2001 From: Maggie Appleton <5599295+MaggieAppleton@users.noreply.github.com> Date: Fri, 28 Aug 2026 10:04:00 +0100 Subject: [PATCH 01/35] Document design system audit approach --- .../2026-08-28-design-system-audit-design.md | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-28-design-system-audit-design.md diff --git a/docs/superpowers/specs/2026-08-28-design-system-audit-design.md b/docs/superpowers/specs/2026-08-28-design-system-audit-design.md new file mode 100644 index 00000000..c8ad339a --- /dev/null +++ b/docs/superpowers/specs/2026-08-28-design-system-audit-design.md @@ -0,0 +1,101 @@ +# Design system audit + +## Aim + +Make Chopin's visual system easier to inspect and harder to drift. The first pass should fix clear, +repeatable inconsistencies while leaving taste-heavy choices visible for a human review. + +The audit covers `apps/web` and the user-facing parts of `packages/editor`. Existing product identity, +behaviour, and terminology stay intact. + +## Current state + +The foundations are stronger than the individual screens suggest. `apps/web/src/theme.css` already +owns the colour, type, spacing, radius, focus, shadow, button, field, and choice-control scales. The +main problem is inconsistent use across roughly 3,400 lines of app and editor CSS. + +Icons currently mix local SVG files with Phosphor React components and explicit sizes from 14px to +24px. At least one pair is byte-identical: `assets/figma/navigation/collapse.svg` and +`assets/icons/conversation-close.svg`. + +Many editor widgets depend on Lexical, Yjs, live stores, or application state. Reimplementing their +markup in a showcase would hide the drift we want to find. + +## Architecture + +Add a development-only `/design-audit` route to `apps/web`. It renders before session loading, so the +catalogue can be reviewed without GitHub authentication. Production builds must not expose or bundle +the route. + +The page has four layers: + +1. **Foundations** – colour, typography, spacing, radii, shadows, icon sizes, and focus treatment. +2. **Controls** – buttons, icon buttons, links, fields, selections, tabs, menus, dropdowns, and their + resting, hover, active, focus, disabled, busy, error, and selected states. +3. **Surfaces** – dialogs, popovers, cards, lists, navigation rows, chat messages, composer elements, + decisions, resolved comments, and loading/empty/error states. +4. **Authored content** – a real read-only editor fixture containing headings, lists, links, image, + formula, Mermaid diagram, code, diff, callout, table, tabs, decision, comment-related prose, and + research states. Where a widget cannot be mounted through the document, use its exported component + with a small typed fixture adapter. + +Catalogue scaffolding belongs in a small `apps/web/src/design-audit/` module. It may compose real +components and fixtures but must not become a second component library. + +## State and interaction + +Sections render all stable visual states side by side. Pointer-only states get a labelled forced-state +sample so screenshots remain deterministic; real controls remain interactive for keyboard and focus +review. Dialogs and menus have both inline specimen and interactive examples. + +Fixtures are local and deterministic. They do not call HTTP, WebSocket, GitHub, or Planner APIs. A +compact viewport control lets the same page expose desktop and narrow arrangements without copying +the app shell. + +## Audit method + +Create a machine-readable inventory alongside the page for: + +- component and state coverage; +- icon source, semantic name, geometry, and duplicate-content hash; +- button and icon-button class combinations; +- shadow, radius, spacing, and colour-token use; and +- items deliberately left for human judgement. + +Inspect the catalogue and authenticated app at desktop and mobile widths. Use one broad screenshot +pass, fix the resulting clear issues in a batch, then use one confirmation pass. This is a bounded +review, not an endless polish loop. + +Clear first-pass fixes include duplicate icon assets, mismatched icon geometry or colour, missing +focus and disabled treatments, undersized targets, accidental one-off spacing, incorrect shadow +levels, and hierarchy that contradicts the shared button variants. Ambiguous brand or composition +changes go into the human-review list instead. + +## Change boundaries + +Normalisation should deepen the existing system: + +- add semantic utilities or small shared components only when at least two real consumers need them; +- migrate consumers in small vertical slices; +- preserve accessible names, keyboard behaviour, responsive behaviour, and editor invariants; and +- avoid compatibility aliases unless persisted data or an external consumer requires one. + +The catalogue stays in the repository as a regression and review surface after the audit. + +## Verification + +The catalogue gets focused render or source-contract tests for route gating and required specimens. +Existing component tests remain authoritative for behaviour. Browser checks cover layout, focus, +menus, dialogs, target sizes, overflow, and screenshots at desktop and mobile widths. + +After edits, run `bun run fix`, focused tests, `bun run types`, `bun run ci`, and the narrowest relevant +Playwright coverage. Run the design-quality detector once over changed UI files after visual work is +finished. + +## Delivery slices + +1. Add the dev-only catalogue shell, inventory, and foundation/control specimens. +2. Add real app surfaces, chat, dialogs, decisions, comments, and loading states. +3. Add the authored-content fixture and research states. +4. Capture the first screenshots and normalise clear token, icon, control, spacing, and surface drift. +5. Confirm desktop/mobile results and publish the remaining human-review list. From 3a2ad0b34d74dfbeeb1d51b4cac4c8d3772570b2 Mon Sep 17 00:00:00 2001 From: Maggie Appleton <5599295+MaggieAppleton@users.noreply.github.com> Date: Fri, 28 Aug 2026 10:05:52 +0100 Subject: [PATCH 02/35] Plan design audit catalogue implementation --- .../2026-08-28-design-audit-catalogue.md | 272 ++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-28-design-audit-catalogue.md diff --git a/docs/superpowers/plans/2026-08-28-design-audit-catalogue.md b/docs/superpowers/plans/2026-08-28-design-audit-catalogue.md new file mode 100644 index 00000000..8bae3669 --- /dev/null +++ b/docs/superpowers/plans/2026-08-28-design-audit-catalogue.md @@ -0,0 +1,272 @@ +# Design Audit Catalogue Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build a development-only page that exposes Chopin's foundations, controls, application surfaces, and authored-content components in deterministic visual states. + +**Architecture:** `apps/web` owns a lazily loaded `/design-audit` route. Small catalogue modules compose real web and editor components; pure fixture data describes coverage. A read-only static editor entry point renders authored MDX through the production dialect and widget renderers without opening a WebSocket. + +**Tech Stack:** React 19, TypeScript, Vite, Tailwind CSS v4, MDXEditor/Lexical, Bun tests. + +--- + +This plan builds the inspection surface. Screenshot findings will produce a separate, evidence-based +normalisation plan; naming exact remediation files before seeing the catalogue would be guesswork. + +### Task 1: Development-only route + +**Files:** +- Create: `apps/web/src/design-audit/route.ts` +- Create: `apps/web/src/design-audit/route.test.ts` +- Create: `apps/web/src/design-audit/page.tsx` +- Modify: `apps/web/src/main.tsx` + +- [ ] **Step 1: Write the route test** + +```ts +import { describe, expect, it } from "bun:test"; +import { isDesignAuditRoute } from "./route"; + +describe("design audit route", () => { + it("exists only in development at its exact path", () => { + expect(isDesignAuditRoute("/design-audit", true)).toBe(true); + expect(isDesignAuditRoute("/design-audit", false)).toBe(false); + expect(isDesignAuditRoute("/design-audit/extra", true)).toBe(false); + }); +}); +``` + +- [ ] **Step 2: Verify the test fails** + +Run: `bun test apps/web/src/design-audit/route.test.ts` +Expected: FAIL because `route.ts` does not exist. + +- [ ] **Step 3: Add the pure route predicate** + +```ts +export function isDesignAuditRoute(pathname: string, development: boolean): boolean { + return development && pathname === "/design-audit"; +} +``` + +- [ ] **Step 4: Add lazy route selection to `main.tsx`** + +Keep the current application root unchanged for ordinary paths. On the audit path, dynamically import +`./design-audit/page` and render its `DesignAuditPage`. The import must be inside the +`import.meta.env.DEV`-guarded branch so a normal production build removes the route chunk. + +- [ ] **Step 5: Add a minimal labelled page and verify** + +```tsx +export function DesignAuditPage() { + return

Chopin design audit

; +} +``` + +Run: `bun test apps/web/src/design-audit/route.test.ts && bun --cwd apps/web run types` +Expected: PASS. + +- [ ] **Step 6: Verify production exclusion and commit** + +Run: `bun --cwd apps/web run build && ! rg -l "Chopin design audit" apps/web/dist` +Expected: build passes and `rg` finds no catalogue string. + +Commit: `git commit -am "Add development design audit route"` + +### Task 2: Coverage inventory and catalogue frame + +**Files:** +- Create: `apps/web/src/design-audit/inventory.ts` +- Create: `apps/web/src/design-audit/inventory.test.ts` +- Create: `apps/web/src/design-audit/frame.tsx` +- Create: `apps/web/src/design-audit/styles.css` +- Modify: `apps/web/src/design-audit/page.tsx` + +- [ ] **Step 1: Write inventory coverage tests** + +Define `AuditGroup`, `AuditItem`, and exported `AUDIT_INVENTORY`. Assert that the inventory contains the +required groups `foundations`, `controls`, `surfaces`, `authored-content`, and the named specimens from +the approved spec. Assert every item has a unique `id`, a source path, and at least one state. + +- [ ] **Step 2: Verify the inventory test fails** + +Run: `bun test apps/web/src/design-audit/inventory.test.ts` +Expected: FAIL because `inventory.ts` does not exist. + +- [ ] **Step 3: Implement the typed inventory** + +```ts +export type AuditItem = { + id: string; + label: string; + source: string; + states: readonly string[]; + judgement?: string; +}; + +export type AuditGroup = { + id: "foundations" | "controls" | "surfaces" | "authored-content"; + label: string; + items: readonly AuditItem[]; +}; +``` + +Populate it with buttons, icon buttons, links, fields, selection, tabs, menus, dropdowns, dialogs, +lists, navigation rows, chat, decisions, resolved comments, loading, empty, error, callout, research, +code, diff, diagram, formula, image, and table. + +- [ ] **Step 4: Build the frame** + +`frame.tsx` owns `AuditSection`, `AuditPlate`, `StateLabel`, the sticky table of contents, and the +desktop/narrow preview-width control. `styles.css` may style only catalogue scaffolding; specimens use +the real application classes. + +- [ ] **Step 5: Run tests and commit** + +Run: `bun test apps/web/src/design-audit/inventory.test.ts && bun --cwd apps/web run types` +Expected: PASS. + +Commit: `git add apps/web/src/design-audit && git commit -m "Map design audit coverage"` + +### Task 3: Foundations and controls + +**Files:** +- Create: `apps/web/src/design-audit/foundations.tsx` +- Create: `apps/web/src/design-audit/controls.tsx` +- Modify: `apps/web/src/design-audit/page.tsx` + +- [ ] **Step 1: Add source-contract assertions to the inventory test** + +Assert each foundation and control inventory ID appears in its specimen module. This is intentionally +a source contract because Bun has no DOM and the repository forbids synthetic layout DOMs. + +- [ ] **Step 2: Render foundations** + +Show the emitted colour tokens, type scale, 4px spacing scale, radii, and the three semantic shadows. +Render all local SVG and Phosphor icons in one 16px default frame, with separate 14px compact and 20px +emphasis examples. Label the byte-identical collapse/conversation-close pair. + +- [ ] **Step 3: Render every control state** + +Use the real `btn`, `btn-md`, `btn-sm`, `btn-icon`, `btn-primary`, `btn-secondary`, `btn-ghost`, +`btn-destructive`, `field`, `field-ghost`, and `choice-control` utilities. Include resting, labelled +forced-hover, labelled forced-active, keyboard-focusable, disabled, busy, selected, invalid, and +read-only examples. Include text links, tablist/tab states, list selection, dropdown, and menu rows. + +- [ ] **Step 4: Verify and commit** + +Run: `bun test apps/web/src/design-audit/inventory.test.ts && bun --cwd apps/web run types` +Expected: PASS. + +Commit: `git add apps/web/src/design-audit && git commit -m "Catalogue foundations and controls"` + +### Task 4: Application surfaces + +**Files:** +- Create: `apps/web/src/design-audit/surfaces.tsx` +- Create: `apps/web/src/design-audit/fixtures.ts` +- Modify: `apps/web/src/design-audit/page.tsx` + +- [ ] **Step 1: Add surface fixtures** + +Create deterministic fixture records for a user, chat messages, repository/document rows, research +requests in queued/running/failed/cancelled/ready states, resolved decisions, resolved comments, and +loading/empty/error content. Use valid protocol shapes and fixed ULIDs/UUIDs. + +- [ ] **Step 2: Compose real surfaces** + +Render actual exported or app-owned components where their API is small: `NavigationDialog`, +`DocumentActionsMenu`, `ConversationToggle`, `TerminalAlert`, `Face`, `SendAction`, `ResearchCard`, +`ResearchComposer`, `QuestionnaireCard`, and chat transcript elements. For tightly coupled surfaces, +extract a reusable presentational component from the current owner rather than copying its markup. + +- [ ] **Step 3: Show layout variants** + +Place dialogs, menus, popovers, cards, navigation/list rows, conversation messages, composer controls, +decision cards, resolved-comment cards, and loading states in labelled plates. Interactive dialog/menu +launchers must restore focus and close with Escape; inline specimens remain open for screenshots. + +- [ ] **Step 4: Verify and commit** + +Run: `bun test apps/web/src/design-audit/inventory.test.ts packages/editor/src/card.test.ts && bun --cwd apps/web run types` +Expected: PASS. + +Commit: `git add apps/web/src/design-audit apps/web/src packages/editor/src && git commit -m "Catalogue application surfaces"` + +### Task 5: Authored-content fixture + +**Files:** +- Create: `packages/editor/src/static-plan-editor.tsx` +- Create: `packages/editor/src/static-plan-editor.test.ts` +- Modify: `packages/editor/package.json` +- Create: `apps/web/src/design-audit/authored-content.tsx` +- Modify: `apps/web/src/design-audit/page.tsx` + +- [ ] **Step 1: Write the static-editor source contract** + +Assert the fixture entry point registers the production widget renderers, uses `dialectPlugins`, uses +the production `widgetsPlugin`, sets `readOnly`, and does not import collaboration or `PlanProvider`. + +- [ ] **Step 2: Implement `StaticPlanEditor`** + +Mount `MDXEditor` with supplied canonical `source`, the same lexical theme and content class as +`PlanEditor`, `dialectPlugins({ core: false })`, `markdownShortcutPlugin()`, and `widgetsPlugin`. +Accept optional `research` and `questions` fixture stores. Export it only from an explicit +`@chopin/editor/static` package subpath. + +- [ ] **Step 3: Create the canonical document fixture** + +Include ordinary prose and links, headings, ordered/unordered/task lists, table, note/tip/warning/danger +callouts, tabs, inline/block maths, plain and TypeScript code, valid and invalid diffs, Mermaid diagram, +image success/failure specimens, a research node, and decision-related prose. Use the real dialect +syntax and fixed valid IDs. + +- [ ] **Step 4: Show sidecar states beside the document** + +Render `ResearchCard` for each lifecycle state, a populated `ResearchComposer`, resolved decision and +comment cards, plus their empty/loading/error variants. This keeps record-owned state authoritative +instead of forging decision records inside MDX. + +- [ ] **Step 5: Verify and commit** + +Run: `bun test packages/editor/src/static-plan-editor.test.ts apps/web/src/design-audit/inventory.test.ts && bun --cwd packages/editor run types && bun --cwd apps/web run types` +Expected: PASS. + +Commit: `git add packages/editor apps/web/src/design-audit && git commit -m "Catalogue authored document components"` + +### Task 6: Browser audit handoff + +**Files:** +- Create: `docs/design-system-audit.md` + +- [ ] **Step 1: Start the real development server** + +Run: `bun run dev` +Expected: the supervisor reports the local application URL. Open `/design-audit` without signing in. + +- [ ] **Step 2: Inspect desktop and mobile in one broad pass** + +Capture full-page and section screenshots at 1440px and 390px. Check overflow, target size, focus, +disabled state, hierarchy, icon geometry/colour, spacing rhythm, surface elevation, modal/menu behaviour, +tabs, authored blocks, and loading states. + +- [ ] **Step 3: Inspect the authenticated application** + +Open the ordinary application root. If it shows GitHub sign-in, stop and notify the user so they can +complete authentication. Once signed in, compare the repository picker, navigation, document, +conversation, decisions, comments, research, dialogs, menus, and loading transitions against the +catalogue at desktop and mobile widths. + +- [ ] **Step 4: Record evidence** + +Create `docs/design-system-audit.md` with a health score, P0–P3 findings with file/line evidence, +positive findings, exact duplicate icons, and a human-judgement parking lot. Each finding must explain +impact and name the token/component boundary that should own the fix. + +- [ ] **Step 5: Write the evidence-based normalisation plan** + +Create a second small-slice plan from the verified P1/P2 findings. Do not include speculative P3 work. + +- [ ] **Step 6: Commit the audit** + +Commit: `git add docs/design-system-audit.md docs/superpowers/plans && git commit -m "Document design system audit findings"` From c039aae31d3b4db03187bbaab2dad0026bba8fe1 Mon Sep 17 00:00:00 2001 From: Maggie Appleton <5599295+MaggieAppleton@users.noreply.github.com> Date: Fri, 28 Aug 2026 10:08:23 +0100 Subject: [PATCH 03/35] Add development design audit route --- apps/web/src/design-audit/page.test.tsx | 14 +++++++++ apps/web/src/design-audit/page.tsx | 9 ++++++ apps/web/src/design-audit/route.test.ts | 11 +++++++ apps/web/src/design-audit/route.ts | 3 ++ apps/web/src/main.tsx | 13 ++++---- .../2026-08-28-design-audit-catalogue.md | 30 +++++++++++-------- 6 files changed, 62 insertions(+), 18 deletions(-) create mode 100644 apps/web/src/design-audit/page.test.tsx create mode 100644 apps/web/src/design-audit/page.tsx create mode 100644 apps/web/src/design-audit/route.test.ts create mode 100644 apps/web/src/design-audit/route.ts diff --git a/apps/web/src/design-audit/page.test.tsx b/apps/web/src/design-audit/page.test.tsx new file mode 100644 index 00000000..e7520904 --- /dev/null +++ b/apps/web/src/design-audit/page.test.tsx @@ -0,0 +1,14 @@ +import { describe, expect, it } from "bun:test"; +import { createElement } from "react"; +import { renderToStaticMarkup } from "react-dom/server"; + +import { DesignAuditPage } from "./page"; + +describe("design audit page", () => { + it("identifies itself as a development inspection surface", () => { + let markup = renderToStaticMarkup(createElement(DesignAuditPage)); + + expect(markup).toContain('data-design-audit=""'); + expect(markup).toContain("Chopin design audit"); + }); +}); diff --git a/apps/web/src/design-audit/page.tsx b/apps/web/src/design-audit/page.tsx new file mode 100644 index 00000000..15b3c78b --- /dev/null +++ b/apps/web/src/design-audit/page.tsx @@ -0,0 +1,9 @@ +export function DesignAuditPage() { + return ( +
+
+

Chopin design audit

+
+
+ ); +} diff --git a/apps/web/src/design-audit/route.test.ts b/apps/web/src/design-audit/route.test.ts new file mode 100644 index 00000000..f7fd5bf8 --- /dev/null +++ b/apps/web/src/design-audit/route.test.ts @@ -0,0 +1,11 @@ +import { describe, expect, it } from "bun:test"; + +import { isDesignAuditRoute } from "./route"; + +describe("design audit route", () => { + it("exists only in development at its exact path", () => { + expect(isDesignAuditRoute("/design-audit", true)).toBe(true); + expect(isDesignAuditRoute("/design-audit", false)).toBe(false); + expect(isDesignAuditRoute("/design-audit/extra", true)).toBe(false); + }); +}); diff --git a/apps/web/src/design-audit/route.ts b/apps/web/src/design-audit/route.ts new file mode 100644 index 00000000..e02d8cff --- /dev/null +++ b/apps/web/src/design-audit/route.ts @@ -0,0 +1,3 @@ +export function isDesignAuditRoute(pathname: string, development: boolean): boolean { + return development && pathname === "/design-audit"; +} diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx index d128d3aa..810d0b5c 100644 --- a/apps/web/src/main.tsx +++ b/apps/web/src/main.tsx @@ -3,6 +3,7 @@ import { createRoot } from "react-dom/client"; import { usePointerCapabilities } from "@chopin/editor/pointer"; import { App } from "./app"; +import { isDesignAuditRoute } from "./design-audit/route"; import { useMotionInput } from "./motion-input"; import { useVisualViewport } from "./viewport"; @@ -22,8 +23,10 @@ function Root() { return ; } -createRoot(root).render( - - - , -); +let content = isDesignAuditRoute(location.pathname, import.meta.env.DEV) + ? import("./design-audit/page").then(({ DesignAuditPage }) => ) + : Promise.resolve(); + +void content.then(value => { + createRoot(root).render({value}); +}); diff --git a/docs/superpowers/plans/2026-08-28-design-audit-catalogue.md b/docs/superpowers/plans/2026-08-28-design-audit-catalogue.md index 8bae3669..9bf7810a 100644 --- a/docs/superpowers/plans/2026-08-28-design-audit-catalogue.md +++ b/docs/superpowers/plans/2026-08-28-design-audit-catalogue.md @@ -63,12 +63,12 @@ export function DesignAuditPage() { } ``` -Run: `bun test apps/web/src/design-audit/route.test.ts && bun --cwd apps/web run types` +Run: `bun test apps/web/src/design-audit/route.test.ts && bun run --cwd apps/web types` Expected: PASS. - [ ] **Step 6: Verify production exclusion and commit** -Run: `bun --cwd apps/web run build && ! rg -l "Chopin design audit" apps/web/dist` +Run: `bun run --cwd apps/web build && ! rg -l "Chopin design audit" apps/web/dist` Expected: build passes and `rg` finds no catalogue string. Commit: `git commit -am "Add development design audit route"` @@ -123,7 +123,7 @@ the real application classes. - [ ] **Step 5: Run tests and commit** -Run: `bun test apps/web/src/design-audit/inventory.test.ts && bun --cwd apps/web run types` +Run: `bun test apps/web/src/design-audit/inventory.test.ts && bun run --cwd apps/web types` Expected: PASS. Commit: `git add apps/web/src/design-audit && git commit -m "Map design audit coverage"` @@ -133,12 +133,15 @@ Commit: `git add apps/web/src/design-audit && git commit -m "Map design audit co **Files:** - Create: `apps/web/src/design-audit/foundations.tsx` - Create: `apps/web/src/design-audit/controls.tsx` +- Create: `apps/web/src/design-audit/specimens.test.tsx` - Modify: `apps/web/src/design-audit/page.tsx` -- [ ] **Step 1: Add source-contract assertions to the inventory test** +- [ ] **Step 1: Write rendered specimen tests** -Assert each foundation and control inventory ID appears in its specimen module. This is intentionally -a source contract because Bun has no DOM and the repository forbids synthetic layout DOMs. +Render the foundation and control sections with `renderToStaticMarkup`. Assert the resulting landmarks, +headings, control roles, accessible names, native disabled attributes, selected state, and invalid state. +The break caught is a catalogue specimen disappearing or losing the same semantics as its real control; +CSS geometry remains a browser test. - [ ] **Step 2: Render foundations** @@ -155,7 +158,7 @@ read-only examples. Include text links, tablist/tab states, list selection, drop - [ ] **Step 4: Verify and commit** -Run: `bun test apps/web/src/design-audit/inventory.test.ts && bun --cwd apps/web run types` +Run: `bun test apps/web/src/design-audit/inventory.test.ts && bun run --cwd apps/web types` Expected: PASS. Commit: `git add apps/web/src/design-audit && git commit -m "Catalogue foundations and controls"` @@ -188,7 +191,7 @@ launchers must restore focus and close with Escape; inline specimens remain open - [ ] **Step 4: Verify and commit** -Run: `bun test apps/web/src/design-audit/inventory.test.ts packages/editor/src/card.test.ts && bun --cwd apps/web run types` +Run: `bun test apps/web/src/design-audit/inventory.test.ts packages/editor/src/card.test.ts && bun run --cwd apps/web types` Expected: PASS. Commit: `git add apps/web/src/design-audit apps/web/src packages/editor/src && git commit -m "Catalogue application surfaces"` @@ -197,15 +200,16 @@ Commit: `git add apps/web/src/design-audit apps/web/src packages/editor/src && g **Files:** - Create: `packages/editor/src/static-plan-editor.tsx` -- Create: `packages/editor/src/static-plan-editor.test.ts` +- Create: `packages/editor/src/static-plan-editor.test.tsx` - Modify: `packages/editor/package.json` - Create: `apps/web/src/design-audit/authored-content.tsx` - Modify: `apps/web/src/design-audit/page.tsx` -- [ ] **Step 1: Write the static-editor source contract** +- [ ] **Step 1: Write the static-editor render contract** -Assert the fixture entry point registers the production widget renderers, uses `dialectPlugins`, uses -the production `widgetsPlugin`, sets `readOnly`, and does not import collaboration or `PlanProvider`. +Render `StaticPlanEditor` with a heading, callout, and code fixture. Assert its labelled read-only +document landmark and source identity render without collaboration status or connection fallback. +The browser pass later asserts the hydrated editor's actual authored blocks. - [ ] **Step 2: Implement `StaticPlanEditor`** @@ -229,7 +233,7 @@ instead of forging decision records inside MDX. - [ ] **Step 5: Verify and commit** -Run: `bun test packages/editor/src/static-plan-editor.test.ts apps/web/src/design-audit/inventory.test.ts && bun --cwd packages/editor run types && bun --cwd apps/web run types` +Run: `bun test packages/editor/src/static-plan-editor.test.tsx apps/web/src/design-audit/inventory.test.ts && bun run --cwd packages/editor types && bun run --cwd apps/web types` Expected: PASS. Commit: `git add packages/editor apps/web/src/design-audit && git commit -m "Catalogue authored document components"` From 1632f51122fc4d79b2fda8810465b6196bd54b44 Mon Sep 17 00:00:00 2001 From: Maggie Appleton <5599295+MaggieAppleton@users.noreply.github.com> Date: Fri, 28 Aug 2026 10:11:15 +0100 Subject: [PATCH 04/35] Map design audit coverage --- apps/web/src/design-audit/frame.test.tsx | 21 +++ apps/web/src/design-audit/frame.tsx | 78 ++++++++ apps/web/src/design-audit/inventory.test.ts | 63 +++++++ apps/web/src/design-audit/inventory.ts | 186 ++++++++++++++++++++ apps/web/src/design-audit/page.test.tsx | 7 + apps/web/src/design-audit/page.tsx | 19 +- apps/web/src/design-audit/styles.css | 185 +++++++++++++++++++ 7 files changed, 554 insertions(+), 5 deletions(-) create mode 100644 apps/web/src/design-audit/frame.test.tsx create mode 100644 apps/web/src/design-audit/frame.tsx create mode 100644 apps/web/src/design-audit/inventory.test.ts create mode 100644 apps/web/src/design-audit/inventory.ts create mode 100644 apps/web/src/design-audit/styles.css diff --git a/apps/web/src/design-audit/frame.test.tsx b/apps/web/src/design-audit/frame.test.tsx new file mode 100644 index 00000000..973802f0 --- /dev/null +++ b/apps/web/src/design-audit/frame.test.tsx @@ -0,0 +1,21 @@ +import { describe, expect, it } from "bun:test"; +import { createElement } from "react"; +import { renderToStaticMarkup } from "react-dom/server"; + +import { AuditPlate, StateLabel } from "./frame"; + +describe("design audit specimen framing", () => { + it("labels a specimen and its represented state", () => { + let markup = renderToStaticMarkup( + createElement( + AuditPlate, + { title: "Primary button", description: "Shared action hierarchy" }, + createElement(StateLabel, null, "Focus"), + ), + ); + + expect(markup).toContain("Primary button"); + expect(markup).toContain("Shared action hierarchy"); + expect(markup).toContain("Focus"); + }); +}); diff --git a/apps/web/src/design-audit/frame.tsx b/apps/web/src/design-audit/frame.tsx new file mode 100644 index 00000000..2808b31e --- /dev/null +++ b/apps/web/src/design-audit/frame.tsx @@ -0,0 +1,78 @@ +import { useState } from "react"; + +import type { ReactNode } from "react"; +import type { AuditGroup } from "./inventory"; + +export function AuditFrame( + { children, groups }: { children: ReactNode; groups: readonly AuditGroup[] }, +) { + let [width, setWidth] = useState<"wide" | "narrow">("wide"); + return ( +
+
+
+

Chopin design audit

+

+ A live map of the foundations, controls, surfaces, and authored content in the + application. +

+
+
+ + +
+
+
+ +
{children}
+
+
+ ); +} + +export function AuditSection( + { children, id, title }: { children: ReactNode; id: string; title: string }, +) { + return ( +
+

{title}

+ {children} +
+ ); +} + +export function AuditPlate( + { children, description, title }: { children?: ReactNode; description?: string; title: string }, +) { + return ( +
+
+

{title}

+ {description ?

{description}

: null} +
+
{children}
+
+ ); +} + +export function StateLabel({ children }: { children: ReactNode }) { + return {children}; +} diff --git a/apps/web/src/design-audit/inventory.test.ts b/apps/web/src/design-audit/inventory.test.ts new file mode 100644 index 00000000..710fa2d6 --- /dev/null +++ b/apps/web/src/design-audit/inventory.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, it } from "bun:test"; + +import { AUDIT_INVENTORY } from "./inventory"; + +const REQUIRED = [ + "colours", + "typography", + "spacing", + "radii", + "shadows", + "icons", + "buttons", + "icon-buttons", + "links", + "fields", + "selections", + "tabs", + "menus", + "dropdowns", + "dialogs", + "lists", + "navigation", + "chat", + "decisions", + "resolved-comments", + "loading", + "empty", + "errors", + "callouts", + "research", + "code", + "diff", + "diagram", + "formula", + "image", + "table", +] as const; + +describe("design audit inventory", () => { + it("covers every required system area exactly once", () => { + expect(AUDIT_INVENTORY.map(group => group.id)).toEqual([ + "foundations", + "controls", + "surfaces", + "authored-content", + ]); + + let items = AUDIT_INVENTORY.flatMap(group => group.items); + let ids = items.map(item => item.id); + expect(new Set(ids).size).toBe(ids.length); + expect(REQUIRED.every(id => ids.includes(id))).toBe(true); + }); + + it("records an inspectable source and state for every specimen", () => { + for (let group of AUDIT_INVENTORY) { + for (let item of group.items) { + expect(item.label.trim().length).toBeGreaterThan(0); + expect(item.source).toMatch(/^(?:apps|packages)\//); + expect(item.states.length).toBeGreaterThan(0); + } + } + }); +}); diff --git a/apps/web/src/design-audit/inventory.ts b/apps/web/src/design-audit/inventory.ts new file mode 100644 index 00000000..8a3f84d8 --- /dev/null +++ b/apps/web/src/design-audit/inventory.ts @@ -0,0 +1,186 @@ +export type AuditItem = { + id: string; + label: string; + source: string; + states: readonly string[]; + judgement?: string; +}; + +export type AuditGroup = { + id: "foundations" | "controls" | "surfaces" | "authored-content"; + label: string; + items: readonly AuditItem[]; +}; + +const STATIC = ["default"] as const; +const INTERACTIVE = ["default", "hover", "active", "focus", "disabled"] as const; +const ASYNC = ["loading", "empty", "error", "ready"] as const; + +export const AUDIT_INVENTORY: readonly AuditGroup[] = [ + { + id: "foundations", + label: "Foundations", + items: [ + { id: "colours", label: "Colour roles", source: "apps/web/src/theme.css", states: STATIC }, + { id: "typography", label: "Typography", source: "apps/web/src/theme.css", states: STATIC }, + { id: "spacing", label: "Spacing", source: "apps/web/src/theme.css", states: STATIC }, + { id: "radii", label: "Radii", source: "apps/web/src/theme.css", states: STATIC }, + { id: "shadows", label: "Elevation", source: "apps/web/src/theme.css", states: STATIC }, + { + id: "icons", + label: "Icons", + source: "apps/web/src/assets/icons", + states: ["default", "active", "disabled"], + judgement: "Confirm the compact, default, and emphasis size roles.", + }, + ], + }, + { + id: "controls", + label: "Controls", + items: [ + { id: "buttons", label: "Buttons", source: "apps/web/src/theme.css", states: INTERACTIVE }, + { + id: "icon-buttons", + label: "Icon buttons", + source: "apps/web/src/theme.css", + states: INTERACTIVE, + }, + { id: "links", label: "Links", source: "apps/web/src/theme.css", states: INTERACTIVE }, + { id: "fields", label: "Fields", source: "apps/web/src/theme.css", states: INTERACTIVE }, + { + id: "selections", + label: "Choices and selections", + source: "apps/web/src/theme.css", + states: ["default", "selected", "focus", "disabled"], + }, + { + id: "tabs", + label: "Tabs", + source: "packages/editor/src/widgets/tabs.tsx", + states: ["default", "selected", "focus", "overflow"], + }, + { + id: "menus", + label: "Menus", + source: "apps/web/src/document-actions-menu.tsx", + states: ["closed", "open", "focus", "disabled"], + }, + { + id: "dropdowns", + label: "Dropdowns", + source: "apps/web/src/document-picker.tsx", + states: ["closed", "open", "selected", "loading", "error"], + }, + ], + }, + { + id: "surfaces", + label: "Application surfaces", + items: [ + { + id: "dialogs", + label: "Dialogs", + source: "apps/web/src/navigation-dialog.tsx", + states: ["open", "busy", "destructive", "error"], + }, + { + id: "lists", + label: "Lists", + source: "apps/web/src/document-picker.tsx", + states: ["default", "hover", "selected", "empty"], + }, + { + id: "navigation", + label: "Navigation", + source: "apps/web/src/project-sidebar.tsx", + states: ["default", "current", "ancestor", "archived", "loading"], + }, + { + id: "chat", + label: "Conversation", + source: "apps/web/src/chat/chat.tsx", + states: ["member", "planner", "tool", "busy", "error"], + }, + { + id: "decisions", + label: "Decisions", + source: "packages/editor/src/decisions.tsx", + states: ["unanswered", "answered", "resolved", "orphaned", "empty"], + }, + { + id: "resolved-comments", + label: "Resolved comments", + source: "packages/editor/src/comments.tsx", + states: ["resolved", "deliberately-empty", "orphaned"], + }, + { id: "loading", label: "Loading", source: "apps/web/src/hosted.tsx", states: ASYNC }, + { + id: "empty", + label: "Empty states", + source: "apps/web/src/repository-picker.tsx", + states: ["first-use", "no-results", "unavailable"], + }, + { + id: "errors", + label: "Errors", + source: "apps/web/src/terminal-alert.tsx", + states: ["inline", "terminal", "recoverable"], + }, + ], + }, + { + id: "authored-content", + label: "Authored content", + items: [ + { + id: "callouts", + label: "Callouts", + source: "packages/editor/src/widgets/callout.tsx", + states: ["note", "tip", "warning", "danger", "focus"], + }, + { + id: "research", + label: "Research", + source: "packages/editor/src/widgets/research.tsx", + states: ["question", "queued", "running", "failed", "cancelled", "ready"], + }, + { + id: "code", + label: "Code blocks", + source: "packages/editor/src/widgets/code-view.tsx", + states: ["plain", "named", "collapsed", "editing"], + }, + { + id: "diff", + label: "Diff blocks", + source: "packages/editor/src/widgets/code-view.tsx", + states: ["valid", "invalid", "collapsed"], + }, + { + id: "diagram", + label: "Diagrams", + source: "packages/editor/src/widgets/code-view.tsx", + states: ["rendered", "source", "invalid"], + }, + { + id: "formula", + label: "Formulae", + source: "packages/editor/src/widgets/render-blocks.tsx", + states: ["inline", "block", "source"], + }, + { + id: "image", + label: "Images", + source: "packages/editor/src/widgets/image.tsx", + states: ["loaded", "unavailable", "missing-alt"], + }, + { + id: "table", + label: "Tables", + source: "packages/editor/src/table/chrome.tsx", + states: ["default", "selected-cell", "toolbar", "overflow"], + }, + ], + }, +]; diff --git a/apps/web/src/design-audit/page.test.tsx b/apps/web/src/design-audit/page.test.tsx index e7520904..986e4283 100644 --- a/apps/web/src/design-audit/page.test.tsx +++ b/apps/web/src/design-audit/page.test.tsx @@ -10,5 +10,12 @@ describe("design audit page", () => { expect(markup).toContain('data-design-audit=""'); expect(markup).toContain("Chopin design audit"); + expect(markup).toContain('aria-label="Design audit sections"'); + expect(markup).toContain('aria-label="Preview wide layout"'); + expect(markup).toContain('aria-label="Preview narrow layout"'); + expect(markup).toContain('data-preview-width="wide"'); + for (let id of ["foundations", "controls", "surfaces", "authored-content"]) { + expect(markup).toContain(`id="${id}"`); + } }); }); diff --git a/apps/web/src/design-audit/page.tsx b/apps/web/src/design-audit/page.tsx index 15b3c78b..80918ee1 100644 --- a/apps/web/src/design-audit/page.tsx +++ b/apps/web/src/design-audit/page.tsx @@ -1,9 +1,18 @@ +import { AuditFrame, AuditSection } from "./frame"; +import { AUDIT_INVENTORY } from "./inventory"; + +import "./styles.css"; + export function DesignAuditPage() { return ( -
-
-

Chopin design audit

-
-
+ + {AUDIT_INVENTORY.map(group => ( + +

+ {group.items.length} component {group.items.length === 1 ? "family" : "families"} +

+
+ ))} +
); } diff --git a/apps/web/src/design-audit/styles.css b/apps/web/src/design-audit/styles.css new file mode 100644 index 00000000..7550c40f --- /dev/null +++ b/apps/web/src/design-audit/styles.css @@ -0,0 +1,185 @@ +.design-audit { + min-height: 100dvh; + padding: max(1.5rem, env(safe-area-inset-top)) max(1.5rem, env(safe-area-inset-right)) + max(2.5rem, env(safe-area-inset-bottom)) max(1.5rem, env(safe-area-inset-left)); + background: var(--color-ground); + color: var(--color-text-primary); +} + +.design-audit-header, +.design-audit-layout { + width: min(100%, 90rem); + margin-inline: auto; +} + +.design-audit-header { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: 2rem; + padding-block-end: 2rem; +} + +.design-audit-header h1, +.design-audit-section h2, +.design-audit-plate h3, +.design-audit-header p, +.design-audit-plate p { + margin: 0; +} + +.design-audit-header h1 { + font-size: var(--text-2xl); + font-weight: 600; + line-height: var(--text-2xl--line-height); + text-wrap: balance; +} + +.design-audit-header p { + max-width: 65ch; + margin-block-start: 0.5rem; + font-size: var(--text-sm); + line-height: var(--text-sm--line-height); + color: var(--color-text-tertiary); + text-wrap: pretty; +} + +.design-audit-width-controls { + display: inline-flex; + flex: 0 0 auto; + gap: 0.125rem; + padding: 0.125rem; + border-radius: var(--radius-lg); + background: var(--color-control); +} + +.design-audit-width-controls [aria-pressed="true"] { + background: var(--color-page); + box-shadow: var(--shadow-resting); + color: var(--color-text-primary); +} + +.design-audit-layout { + display: grid; + grid-template-columns: 10rem minmax(0, 1fr); + align-items: start; + gap: 2rem; +} + +.design-audit-nav { + position: sticky; + top: 1.5rem; + display: grid; + gap: 0.25rem; +} + +.design-audit-nav a { + border-radius: var(--radius-md); + padding: 0.375rem 0.5rem; + font-size: var(--text-sm); + font-weight: 500; + color: var(--color-text-tertiary); + text-decoration: none; +} + +.design-audit-nav a:hover { + background: var(--color-hover); + color: var(--color-text-primary); +} + +.design-audit-preview { + width: 100%; + min-width: 0; + margin-inline: auto; +} + +.design-audit-preview[data-preview-width="narrow"] { + max-width: 24.375rem; +} + +.design-audit-section { + padding-block: 2.5rem; + border-block-start: var(--edge-width) solid var(--color-edge); +} + +.design-audit-section:first-child { + padding-block-start: 0; + border-block-start: 0; +} + +.design-audit-section h2 { + font-size: var(--text-xl); + font-weight: 600; + line-height: var(--text-xl--line-height); + text-wrap: balance; +} + +.design-audit-section-summary { + margin: 0.375rem 0 0; + font-size: var(--text-sm); + color: var(--color-text-quaternary); +} + +.design-audit-plate { + padding-block: 1.5rem; +} + +.design-audit-plate > header { + display: grid; + gap: 0.25rem; + margin-block-end: 1rem; +} + +.design-audit-plate h3 { + font-size: var(--text-lg); + font-weight: 600; + line-height: var(--text-lg--line-height); + text-wrap: balance; +} + +.design-audit-plate p { + max-width: 70ch; + font-size: var(--text-sm); + color: var(--color-text-tertiary); + text-wrap: pretty; +} + +.design-audit-specimen { + min-width: 0; +} + +.design-audit-state { + display: inline-flex; + align-items: center; + min-height: 1.5rem; + padding-inline: 0.5rem; + border-radius: 9999px; + background: var(--color-gray-200); + font-size: var(--text-sm); + font-weight: 500; + color: var(--color-text-tertiary); +} + +@media (max-width: 639px) { + .design-audit { + padding-right: max(1rem, env(safe-area-inset-right)); + padding-left: max(1rem, env(safe-area-inset-left)); + } + + .design-audit-header { + align-items: flex-start; + flex-direction: column; + gap: 1rem; + } + + .design-audit-layout { + display: block; + } + + .design-audit-nav { + position: static; + display: flex; + overflow-x: auto; + margin-block-end: 2rem; + } +} From 0ea1b2855179420ccbd782646b52e47055b80ccb Mon Sep 17 00:00:00 2001 From: Maggie Appleton <5599295+MaggieAppleton@users.noreply.github.com> Date: Fri, 28 Aug 2026 10:16:49 +0100 Subject: [PATCH 05/35] Catalogue design foundations and controls --- apps/web/src/design-audit/controls.css | 287 +++++++++++++++++++ apps/web/src/design-audit/controls.tsx | 277 ++++++++++++++++++ apps/web/src/design-audit/foundations.css | 185 ++++++++++++ apps/web/src/design-audit/foundations.tsx | 133 +++++++++ apps/web/src/design-audit/frame.tsx | 9 +- apps/web/src/design-audit/icons.tsx | 133 +++++++++ apps/web/src/design-audit/page.tsx | 16 +- apps/web/src/design-audit/specimens.test.tsx | 40 +++ apps/web/src/design-audit/styles.css | 3 + 9 files changed, 1077 insertions(+), 6 deletions(-) create mode 100644 apps/web/src/design-audit/controls.css create mode 100644 apps/web/src/design-audit/controls.tsx create mode 100644 apps/web/src/design-audit/foundations.css create mode 100644 apps/web/src/design-audit/foundations.tsx create mode 100644 apps/web/src/design-audit/icons.tsx create mode 100644 apps/web/src/design-audit/specimens.test.tsx diff --git a/apps/web/src/design-audit/controls.css b/apps/web/src/design-audit/controls.css new file mode 100644 index 00000000..3e4024dc --- /dev/null +++ b/apps/web/src/design-audit/controls.css @@ -0,0 +1,287 @@ +.design-audit-button-families, +.design-audit-button-families > div, +.design-audit-control-state { + display: grid; +} + +.design-audit-button-families { + gap: 1.5rem; +} + +.design-audit-button-families > div { + gap: 0.75rem; +} + +.design-audit-button-families h4 { + margin: 0; + font-size: var(--text-sm); + font-weight: 600; + color: var(--color-text-secondary); +} + +.design-audit-control-grid, +.design-audit-button-sizes { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr)); + gap: 0.75rem; +} + +.design-audit-control-state { + align-content: start; + justify-items: start; + gap: 0.5rem; +} + +.design-audit-control-state > div { + display: flex; + min-height: 2.5rem; + align-items: center; +} + +.design-audit-busy-mark { + width: 0.5rem; + height: 0.5rem; + margin-inline-end: 0.375rem; + border-radius: 9999px; + background: currentColor; +} + +.design-audit [data-audit-state="hover"].btn-primary { + background: var(--color-brand-hover); +} + +.design-audit [data-audit-state="active"].btn-primary { + background: var(--color-brand-active); +} + +.design-audit [data-audit-state="hover"].btn-secondary { + background: var(--color-gray-300); +} + +.design-audit [data-audit-state="active"].btn-secondary { + background: var(--color-gray-400); +} + +.design-audit [data-audit-state="hover"].btn-ghost { + background: var(--color-gray-200); +} + +.design-audit [data-audit-state="active"].btn-ghost { + background: var(--color-gray-300); +} + +.design-audit [data-audit-state="hover"].btn-destructive { + background: var(--color-destructive-hover); +} + +.design-audit [data-audit-state="active"].btn-destructive { + background: var(--color-destructive-active); +} + +.design-audit [data-audit-state="focus"] { + outline: var(--focus-ring-width) solid var(--focus-ring-color); + outline-offset: var(--focus-ring-offset); +} + +.design-audit-link { + border-radius: var(--radius-sm); + font-size: var(--text-sm); + font-weight: 500; + color: var(--color-brand-ink); + text-decoration: underline; + text-decoration-color: color-mix(in srgb, currentColor 45%, transparent); + text-decoration-thickness: 1px; + text-underline-offset: 0.18em; +} + +.design-audit-link[data-audit-state="hover"] { + color: var(--color-brand-hover); + text-decoration-color: currentColor; +} + +.design-audit-link[data-audit-state="active"] { + color: var(--color-brand-active); +} + +.design-audit-link[aria-disabled="true"] { + color: var(--color-text-quaternary); + text-decoration: none; +} + +.design-audit-field-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1rem; +} + +.design-audit-field-grid label { + display: grid; + gap: 0.375rem; + font-size: var(--text-sm); + font-weight: 500; + color: var(--color-text-secondary); +} + +.design-audit-field-grid :is(input, select, textarea) { + width: 100%; + min-width: 0; + padding: 0.375rem 0.5rem; + font-size: var(--text-sm); + line-height: var(--text-sm--line-height); +} + +.design-audit-field-wide { + grid-column: 1 / -1; +} + +.design-audit-choice-row { + display: flex; + flex-wrap: wrap; + gap: 1rem 1.5rem; + margin-block-end: 1rem; +} + +.design-audit-choice-row label { + display: inline-flex; + align-items: center; + gap: 0.5rem; + font-size: var(--text-sm); + color: var(--color-text-secondary); +} + +.design-audit-choice-row input { + width: 1rem; + height: 1rem; +} + +.design-audit-selection-list, +.design-audit-menu, +.design-audit-dropdown-list { + width: min(100%, 18rem); + padding: 0.25rem; + border-radius: var(--radius-lg); + background: var(--color-page); + box-shadow: var(--shadow-overlay); +} + +.design-audit-selection-list [role="option"], +.design-audit-dropdown-list [role="option"] { + display: flex; + min-height: 2rem; + align-items: center; + gap: 0.5rem; + border-radius: var(--radius-md); + padding-inline: 0.5rem; + font-size: var(--text-sm); + color: var(--color-text-secondary); +} + +.design-audit-selection-list [aria-selected="true"], +.design-audit-dropdown-list [aria-selected="true"] { + background: var(--color-selected); + color: var(--color-text-primary); +} + +.design-audit-selection-list [aria-disabled="true"] { + color: var(--color-text-quaternary); +} + +.design-audit-tabs { + display: flex; + width: fit-content; + max-width: 100%; + gap: 0.25rem; + overflow-x: auto; + border-block-end: var(--edge-width) solid var(--color-edge); +} + +.design-audit-tabs button { + position: relative; + min-height: 2rem; + padding-inline: 0.5rem; + font-size: var(--text-sm); + font-weight: 500; + white-space: nowrap; + color: var(--color-text-tertiary); +} + +.design-audit-tabs button[aria-selected="true"] { + color: var(--color-text-primary); +} + +.design-audit-tabs button[aria-selected="true"]::after { + position: absolute; + right: 0.5rem; + bottom: -1px; + left: 0.5rem; + height: 2px; + border-radius: 9999px; + background: var(--color-brand); + content: ""; +} + +.design-audit-tabs button:disabled { + color: var(--color-text-quaternary); +} + +.design-audit-tabs button span { + margin-inline-start: 0.25rem; + font-variant-numeric: tabular-nums; +} + +.design-audit-menu { + display: grid; +} + +.design-audit-menu button { + min-height: 2rem; + border-radius: var(--radius-md); + padding-inline: 0.5rem; + font-size: var(--text-sm); + text-align: left; + color: var(--color-text-secondary); +} + +.design-audit-menu button:hover { + background: var(--color-hover); + color: var(--color-text-primary); +} + +.design-audit-menu .design-audit-menu-destructive { + margin-block-start: 0.25rem; + border-block-start: var(--edge-width) solid var(--color-edge); + border-radius: 0 0 var(--radius-md) var(--radius-md); + color: var(--color-destructive-ink); +} + +.design-audit-dropdown-row { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + gap: 2rem; +} + +.design-audit-dropdown-trigger { + display: inline-flex; + min-height: 2rem; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding-inline: 0.5rem; + font-size: var(--text-sm); +} + +.design-audit-dropdown-open { + display: grid; + gap: 0.25rem; +} + +@media (max-width: 639px) { + .design-audit-field-grid { + grid-template-columns: 1fr; + } + + .design-audit-field-wide { + grid-column: auto; + } +} diff --git a/apps/web/src/design-audit/controls.tsx b/apps/web/src/design-audit/controls.tsx new file mode 100644 index 00000000..74667aed --- /dev/null +++ b/apps/web/src/design-audit/controls.tsx @@ -0,0 +1,277 @@ +import { CaretDownIcon, CheckIcon, PlusIcon } from "@phosphor-icons/react"; + +import { AuditPlate, StateLabel } from "./frame"; + +import type { ReactNode } from "react"; + +const STATES = ["Default", "Hover", "Active", "Focus", "Disabled"] as const; + +function StateSample({ children, state }: { children: ReactNode; state: string }) { + return ( +
+ {state} +
{children}
+
+ ); +} + +function ButtonRow( + { className, label }: { className: string; label: string }, +) { + return ( +
+ {STATES.map(state => ( + + + + ))} +
+ ); +} + +export function Controls() { + return ( + <> + +
+
+

Primary

+ +
+
+

Secondary

+ +
+
+

Ghost

+ +
+
+

Destructive

+ +
+
+ + + + + + + + + +
+
+
+ + +
+ {STATES.map(state => ( + + + + ))} +
+
+ + +
+ {STATES.map(state => ( + + + Read the document + + + ))} +
+
+ + +
+ + + + + + +