From 68c8b2ca2db871315ee29fb922087ba0795112b2 Mon Sep 17 00:00:00 2001 From: Jarren San Jose Date: Fri, 18 Sep 2026 15:43:57 -0700 Subject: [PATCH] chore(repo): remove CLAUDE.md shims in favor of AGENTS.md Claude Code now reads AGENTS.md directly, so the CLAUDE.md files that only import another file are redundant. Removes the root CLAUDE.md (@AGENTS.md) and, in packages/swingset, replaces the AGENTS.md shim (@CLAUDE.md) with the real doc by renaming CLAUDE.md to AGENTS.md. Updates the mosaic skill reference and the prettierignore entry to the new path, and adds an empty changeset. --- .changeset/remove-claude-md-shims.md | 2 + .claude/skills/mosaic/SKILL.md | 2 +- .prettierignore | 2 +- CLAUDE.md | 1 - packages/swingset/AGENTS.md | 290 ++++++++++++++++++++++++++- packages/swingset/CLAUDE.md | 289 -------------------------- 6 files changed, 293 insertions(+), 293 deletions(-) create mode 100644 .changeset/remove-claude-md-shims.md delete mode 100644 CLAUDE.md delete mode 100644 packages/swingset/CLAUDE.md diff --git a/.changeset/remove-claude-md-shims.md b/.changeset/remove-claude-md-shims.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/remove-claude-md-shims.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.claude/skills/mosaic/SKILL.md b/.claude/skills/mosaic/SKILL.md index c69d35916f1..cba41e04281 100644 --- a/.claude/skills/mosaic/SKILL.md +++ b/.claude/skills/mosaic/SKILL.md @@ -81,7 +81,7 @@ controller, and view layers, then verifies parity with `parity-audit.md`. ## Documenting a component in swingset -`packages/swingset/CLAUDE.md` is the house style — archetypes, required section +`packages/swingset/AGENTS.md` is the house style — archetypes, required section order, `meta` conventions. One rule on top of it, because it is the one agents get wrong: diff --git a/.prettierignore b/.prettierignore index da490cc898c..6b6d0112b69 100644 --- a/.prettierignore +++ b/.prettierignore @@ -25,4 +25,4 @@ packages/shared/src/compiled renovate.json5 # Frozen snapshots of TypeDoc-generated MDX; must match raw `extract-methods.mjs` output. .typedoc/__tests__/__snapshots__/ -CLAUDE.md +packages/swingset/AGENTS.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 43c994c2d36..00000000000 --- a/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -@AGENTS.md diff --git a/packages/swingset/AGENTS.md b/packages/swingset/AGENTS.md index 61769c981cf..957dc069075 100644 --- a/packages/swingset/AGENTS.md +++ b/packages/swingset/AGENTS.md @@ -1 +1,289 @@ -@CLAUDE.md +# AGENTS.md + +This file provides guidance to Coding Agents when working with code in this repository. + +`@clerk/swingset` is a private (unpublished) component explorer — a Storybook-like app — for the **Mosaic** design system that lives in `@clerk/mosaic`. It is a Next.js App Router app that renders Mosaic components interactively with live knobs and design-token overrides. + +## Commands + +Run from the monorepo root or scope with `--filter @clerk/swingset`: + +``` +pnpm run dev:swingset # next dev on port 6006 +pnpm build --filter @clerk/swingset # next build +pnpm format --filter @clerk/swingset # format-package.mjs (--check for CI) +``` + +There are no tests or lint scripts in this package, yet. + +## Key architecture facts + +These require reading several files together; the `README.md` covers the step-by-step "add a component" workflow. + +- **Consumes Mosaic from source, not build.** `@clerk/mosaic` is aliased to `../mosaic/src` in *two* places that must stay in sync: `next.config.mjs` (webpack `resolve.alias`) and `tsconfig.json` (`paths`). Editing Mosaic source in `packages/mosaic` reflects live in swingset's dev server — no rebuild of `@clerk/mosaic` needed. + +- **Knobs are generated from a story's declared variant surface.** A story's `meta.styles` is a hand-written `{ _variants, _defaultVariants }` object describing the component's variant props — StyleX compiles its styles away, so there is no runtime recipe to derive this from. `lib/generateKnobs.ts` turns each variant into a control: variants whose keys are only `true`/`false` become boolean toggles, everything else becomes a select. Knob values are passed as props straight into the story component. This is why story functions take `Record` and cast to the real prop type. + +- **`lib/registry.ts` is the single source of truth for which components exist**, and they are imported *explicitly* (never `import *`) so sidebar order is deterministic. `getSidebarGroups`, `getModuleBySlug`, and slugging (`lib/slug.ts`, from `meta.title`) read from it. Adding a component touches up to three wiring points: `registry.ts` (sidebar entry + per-page playground lookup), `DocsViewer.tsx`'s `docModules` map (MDX docs), and the hardcoded redirect in `app/page.tsx`. + - ⚠️ **Add each new import and its first usage in the same edit.** The on-save lint-fix (`unused-imports/no-unused-imports` is an `error`) deletes any import that isn't referenced yet, so importing a story export in `registry.ts` (or a component in a `*.stories.tsx`) *before* the code that uses it silently drops the import and you get `X is not defined` at runtime. After wiring, `grep` the new symbol to confirm both the import and its use survived. (Repo-wide footgun; see `clerk-monorepo` skill `references/setup-and-footguns.md`.) + +- **Routing.** Each component is a single page: `/components/[component]` renders its MDX overview via `DocsViewer`. There are no per-story sub-pages — the interactive playground lives *inside* the overview. `app/page.tsx` is a static redirect (currently to `/components/button`) because `registry.ts` eagerly imports story modules (client components / `createContext`), so registry-derived data can't be computed in a Server Component. `DocsViewer` also renders a "View source" link (`ViewSource.tsx`) from `meta.source` — a repo-root-relative path turned into a GitHub URL by `lib/source.ts`. + +- **Shared playground state.** `DocsViewer` wraps each overview in a `PlaygroundProvider` (`PlaygroundContext.tsx`), keyed by slug and seeded from the component's `meta` via `getModuleBySlug`. It owns the knob values (props). The `` and the interactive `` both read/write this single context, so editing a prop in the table updates the preview above it. + +- **Every story renders inside `MosaicProvider`.** `StoryPreview` (the MDX ``) renders a named story with the playground's knob values as props and exposes a Reset button. `StoryEmbed` (the MDX ``) renders a single static variation with default knob values and no controls. + - The header's **RTL** switch (`DirectionToggle`, state in `DirectionProvider`) sets `dir` on ``. It has to sit that high because popover and menu popups portal to ``, so a `dir` on the preview container would never reach them; swingset's own chrome flips along with the story. + +- **The prop table is the knob surface.** `PropTable` (MDX ``) derives rows from `meta.styles._variants`/`_defaultVariants`, then appends the `className` + `style` escape-hatch rows every Mosaic component accepts. Each variant row renders a `KnobControl` in its **Value** column, seeded with the prop's default and bound to the playground context. The escape-hatch rows and `extra` stay static. + +- **MDX.** `mdx-components.tsx` injects custom components into all MDX: `` (→ `StoryPreview`), `` (→ `StoryEmbed`, static), `` (→ interactive `PropTable`), `` (→ `UsageBlock`, a live code snippet that reflects the current knob values), and a `
` override routing fenced code through Shiki (`CodeBlock`). `next.config.mjs` configures `remark-gfm` and `rehype-raw` (with MDX node pass-through) so raw HTML in tables works.
+
+- **`` examples can show their source in a collapsible code footer.** When a story module exposes its own source as `__source` — via a `?raw` self-import (`export { default as __source } from './x.stories?raw'`) — `StoryEmbed` runs `extractStorySource` (`lib/extractStorySource.ts`) to pull the *previewed story function's* source out of that raw text, then `toUsageSnippet` (`lib/exampleSnippet.ts`) to reduce that knob harness to a clean usage snippet (unwraps `export function …() { return (…) }` down to the returned JSX and strips the `{...knobsAsProps(props)}` / `{...props}` knob plumbing), and renders a `CodeFooter` (`CodeFooter.tsx`): a "View code" toggle that's collapsed by default and reveals the snippet with a height animation (Base UI's `--collapsible-panel-height` + `data-starting/ending-style`). It's **opt-in per module** — only modules that export `__source` get a footer, and it's keyed to whichever story `name` the `` renders, so each example shows its own code. Shiki highlighting is shared with the `
`/`CodeBlock` path through the `useShikiHtml` hook. A `` can carry both a code footer and a `composition` footer; they stack under the preview.
+  - The `?raw` query is wired in `next.config.mjs`: an `asset/source` rule handles `?raw` imports, and — crucially — a recursive `excludeRawQuery` pass adds `resourceQuery: { not: [/raw/] }` to every *other* loader so Next's SWC loader doesn't compile the file first (otherwise `__source` would contain `_jsxDEV(…)` output instead of the authored source).
+
+- **Two component layers.** `src/components/ui/*` are shadcn/ui primitives (`components.json`, `base-nova` style, neutral base) used for swingset's *own* chrome (sidebar, tabs, inputs). The components being *documented* come from `@clerk/mosaic`. Don't confuse the two.
+
+## Documenting Mosaic components
+
+The facts above explain *how the app works* and `README.md` covers the *mechanical wiring* of adding a component (registry, `docModules`, redirect). This section is the **house style** — *what to write* once the wiring is in place, so every component page reads consistently.
+
+A component's docs are two files in `src/stories/`:
+
+- `.stories.tsx` — the live demos (named React exports + a `meta`).
+- `.mdx` — the prose page that embeds those demos via the injected MDX tags (``, ``, ``, ``; see the **MDX** architecture note above for the mechanism).
+
+Pick the archetype below by the component's **layer** (its `meta.group`), then follow that archetype's required MDX section order exactly. Same archetype → same headings in the same order, every time. That uniformity is the whole point.
+
+### Layers
+
+`meta.group` places an entry in one of these layers. Group order follows first appearance in the `registry` array. The sidebar sorts `Blocks`, `Components`, `Primitives`, `Styles`, and `Hooks` alphabetically by `title`; `User Button`, `User Profile`, `Reverification`, and `Localization` render in registry order. Within a group, an optional `meta.navigation.category` sub-groups entries under a small collapsible subheading (e.g. `User Profile` splits into `Panels` and `Sections`), collapsed by default unless it contains the active page; category order also follows first appearance in the registry, and uncategorized entries render with no subheading (list them before the categorized ones). Use these exact group strings:
+
+| Group        | What lives here                                                | Archetype |
+| ------------ | -------------------------------------------------------------- | --------- |
+| `User Button` | Composed flow UI (e.g. `UserButton`)                          | C         |
+| `User Profile` | Composed flow UI (e.g. `UserProfileProfilePanel`)             | C         |
+| `Components` | Styled Mosaic components — simple, with a flat variant surface (`Button`, `Input`), or compound (`Card`, `Field`, `Menu`, `Popover`) | A         |
+| `Primitives` | Headless `@clerk/headless` primitives (`Accordion`)            | B         |
+| `Styles`     | Atomic styles that ship as StyleX atoms, not components (`Scroll Area`) | B (adapted) |
+| `Hooks`      | Headless hooks (`useDataTable`)                                | B (adapted) |
+| `Localization` | The `localization` prop on `MosaicProvider`: catalogs, overrides, locale, and the message helpers | B (adapted) |
+
+`User Button` / `User Profile` → `Components` → `Primitives` runs high-level-composition → low-level-primitive. Composed layers are documented as compositions of lower layers (archetype C); leaf layers (Components, Primitives) get full prop/knob docs (archetypes A and B).
+
+`Styles` and `Hooks` are the non-component layers: there is no element to knob, so they follow
+archetype B's shape (Example → Usage → Parts → Styling) with `Props` replaced by whatever the export
+actually surfaces — an argument table for a style function, a return-value table for a hook. A
+`Styles` entry documents the tokens its atoms read, since those tokens _are_ its API; the
+`Hooks` entry (`use-data-table.stories.tsx`) is `meta` alone, with no story exports at all, which is
+the minimum a section entry needs.
+
+Archetype A has two forms, chosen by whether the component exposes a single flat set of variant props: **simple** components (`Button`, `Input`) are knob-driven; **compound** components (`Card`, `Field`, `Menu`, `Popover`) have no flat variant props to knob, so they're documented like a primitive but themed. Both are detailed under Archetype A below.
+
+### `meta` conventions (all archetypes)
+
+```ts
+export const meta: StoryMeta = {
+  group: 'Components', // exact group string from the table
+  title: 'Button', // drives slug + the page 

+ label: 'Delete Org', // optional friendlier sidebar text + status: 'stable', // maturity dot in sidebar + page badge; omit to show no status + source: 'packages/mosaic/src/components/button/button.tsx', // repo-root path → "View source" + styles: { + // Hand-written variant surface — archetype A · simple only + _variants: { variant: { primary: {}, outline: {} }, size: { sm: {}, md: {} } }, + _defaultVariants: { variant: 'primary', size: 'md' }, + }, +}; +``` + +- `title` is the component's export name; it produces the slug and is what readers match against code. Set `label` only when the sidebar should read differently (the slug and page heading still come from `title`). +- `status` marks the entry's maturity — `'stable'`, `'wip'`, or `'todo'` (planned, not yet implemented; e.g. `Table`) — rendered as a colored dot (green / amber / gray) to the left of the entry in the sidebar, and as a dot-plus-label badge to the right of the page's `# Title` h1. It is explicit: omitting it displays no status anywhere (not every entry carries one, e.g. `Scroll Area`). A todo entry is `meta` alone plus a one-paragraph MDX page; it skips its archetype's required sections (and `source`) until the component exists. +- `substatus` optionally says *why* a wip entry is wip — one of `'needs wire-up'` (view runs on mock data, not yet connected to its model/controller), `'needs design'`, `'needs a11y'`, `'needs docs'`, `'needs polish'`. It renders in the page badge (`wip: needs wire-up`) and the sidebar dot's tooltip. Most wip entries won't need one; it is ignored on stable entries. +- `source` is always a path **relative to the monorepo root**, pointing at the file that exports the documented component. Always set it — it powers the "View source" link. +- `styles` declares the component's variant props and is **required for archetype A's simple (knob-driven) form** (it generates the knobs and the ``). Keep it in sync with the component's real prop union by hand — StyleX compiles its styles away, so nothing derives it for you. Omit it for compound A components, and for B and C. + +Always import the component explicitly — never `import *`. + +### Archetype A — styled component (`Components`) + +A styled Mosaic component. Which of the two forms below applies is decided by the component's shape, not by preference: if it exposes a single flat set of variant props (declared as `meta.styles`), use the **simple** form; if it's compound — a set of parts with no flat variant props (`Card`, `Field`, `Menu`, `Popover`) — use the **compound** form. + +**Every `Components`-layer story file exposes its source so each `` example renders a code footer.** Add the self-import once, right after the imports: + +```ts +// Exposes this file's own source (via the `?raw` webpack rule) so each `` example +// renders a code footer with its function's source. See `StoryModule.__source`. +export { default as __source } from './.stories?raw'; +``` + +That's all the wiring needed — `StoryEmbed` picks `__source` up automatically and renders a collapsible "View code" footer keyed to each example's story function (see the `` code-footer architecture note above). No MDX change is required; keep authoring `` as before. This applies to both A forms (simple and compound) and to every example a Components page ships. + +#### A · simple — flat variant props (`Button`, `Input`) + +Has a declared variant surface, so the page is **knob-driven**: an interactive canvas plus a generated prop table. Required MDX section order: + +```mdx +import * as ButtonStories from './button.stories'; + +# Button + + + +## Playground + + + +## Props + + + +## Usage + + + Click me + + +--- + +## Examples + +### Sizes + + + +### Disabled + + +``` + +- **Playground / Props / Usage are mandatory and always in this order.** The three share one playground state: editing a row in `` re-renders `` above it and regenerates the `` snippet below it. +- The story file exports a primary demo (rendered by ``) plus one named export per variation under **Examples**. Each story takes `props: Record` and casts through a local `knobsAsProps` helper — knobs are dynamically typed, the component isn't. +- Use ``'s `extra` for documenting non-variant props; the `className` + `style` escape-hatch rows are appended for you. +- Use `` to pin static, non-knob props in the generated snippet. +- `` renders `Prop | Type | Default | Value`: the **Default** column is filled from `meta.styles._defaultVariants`, and the **Value** column is the live knob seeded with that default. No per-row default annotation is needed; see _Document the default value_ under Archetype B. + +#### A · compound — parts, no flat variant props (`Card`, `Field`, `Menu`, `Popover`) + +A compound styled component (`Popover.Root`/`Popover.Popup`/…) has no single flat prop interface to knob, so there's no `` or ``. Document it like a primitive (archetype B) but themed — the difference is the **Styling** section, which lists each part's `.cl-` class and `data-` attributes rather than saying "bring your own CSS". Required MDX section order: + +```mdx +import * as PopoverStories from './popover.component.stories'; + +# Popover + + + +## Example + + + +## Usage + + + +## Parts + + + +## Styling + + +``` + +The story is `meta` (no `styles`) plus a single `Default` export that renders the composed parts. The file pair is named `.component.stories.tsx` / `.component.mdx` so it doesn't collide with the headless `Primitives` entry of the same title (e.g. `Menu`, `Popover` exist in both layers); the `docModules` map disambiguates by group. + +### Archetype B — headless primitive (`Primitives`) + +No styles, so there's no knob canvas. The single demo renders the primitive **raw (unstyled)** to show only behavior, state, and ARIA wiring. The prop/styling tables are **hand-written** (there is no variant surface to derive them from). Required MDX section order: + +```mdx +# Accordion + + + +## Example + + + +## Usage + + + +## Parts + + + +## Props + + + +## Styling + + +``` + +The story is `meta` (no `styles`) plus a single `Default` export that renders the primitive unstyled. Don't add a `` or `` — primitives have neither knobs nor a variant surface to drive them. + +**Document the default value for every prop in a dedicated Default column.** Every props table — auto and hand-written — has a **Default** column; the `Type` stays a plain union/enum and the default is named in its own column (the convention every component-doc site and TypeDoc's `@default` tag follow), never inlined into the type. The auto `` renders `Prop | Type | Default | Value` and fills Default from `meta.styles._defaultVariants` (the **Value** column is the live knob seeded with that default); hand-written tables render `Prop | Type | Default | Description` and fill it by hand. Name the default member (`'base'`, `'multiple'`, `'bottom-start'`); use `—` when there is no default (a controlled-only or required prop) and append `(required)` for required props; when the default is behavioral rather than a literal, state it in words (`inherits Root`, `falls back to value`). + +### Archetype C — composed layer (`User Button`, `User Profile`) + +These compose lower layers, so the docs lead with the composition rather than knobs. Required MDX: + +```mdx +import * as UserButtonStories from './user-button.stories'; + +# UserButton + + + + +``` + +- The `composition` array names each lower-layer component this one builds on, with a link to that component's page and its `layer`. List every direct dependency so the layering is navigable. +- The story is `meta` plus a single `Default` export that renders the composed UI with no knobs. + +### Writing the prose + +- Keep the intro to one short, present-tense paragraph: what the thing is and what it's for. For primitives, say explicitly that it's headless and ships no styles. +- Prose should add what a demo can't — behavior, accessibility, when to reach for it — not restate prop names already in the table. +- Describe the API as it is; leave the reasoning that produced it out. No "not a `size`, because…", no rejected alternatives, no history of what a prop used to be — that belongs in a code comment or the PR. The docs get the conclusion, stated plainly and briefly. +- Lead every page with the heading hierarchy its archetype prescribes; don't invent new top-level sections or reorder them. Consistency across pages is the goal. + +### Before you finish + +- [ ] `meta.source` is set to a repo-root-relative path. +- [ ] Story renders. +- [ ] `Components`-layer story files export `__source` (the `?raw` self-import) so every `` example gets a "View code" footer. +- [ ] MDX sections match the archetype's required order exactly. +- [ ] Every props-table row states its default in the **Default** column (auto `` fills it from `meta.styles._defaultVariants`; `—` / `(required)` when none). +- [ ] Wiring done per `README.md`: `registry.ts`, `DocsViewer.tsx`'s `docModules`, and the `app/page.tsx` redirect if this is now the first component. +- [ ] `pnpm format --filter @clerk/swingset` is clean. diff --git a/packages/swingset/CLAUDE.md b/packages/swingset/CLAUDE.md deleted file mode 100644 index 402ebcd5b59..00000000000 --- a/packages/swingset/CLAUDE.md +++ /dev/null @@ -1,289 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Coding Agents when working with code in this repository. - -`@clerk/swingset` is a private (unpublished) component explorer — a Storybook-like app — for the **Mosaic** design system that lives in `@clerk/mosaic`. It is a Next.js App Router app that renders Mosaic components interactively with live knobs and design-token overrides. - -## Commands - -Run from the monorepo root or scope with `--filter @clerk/swingset`: - -``` -pnpm run dev:swingset # next dev on port 6006 -pnpm build --filter @clerk/swingset # next build -pnpm format --filter @clerk/swingset # format-package.mjs (--check for CI) -``` - -There are no tests or lint scripts in this package, yet. - -## Key architecture facts - -These require reading several files together; the `README.md` covers the step-by-step "add a component" workflow. - -- **Consumes Mosaic from source, not build.** `@clerk/mosaic` is aliased to `../mosaic/src` in *two* places that must stay in sync: `next.config.mjs` (webpack `resolve.alias`) and `tsconfig.json` (`paths`). Editing Mosaic source in `packages/mosaic` reflects live in swingset's dev server — no rebuild of `@clerk/mosaic` needed. - -- **Knobs are generated from a story's declared variant surface.** A story's `meta.styles` is a hand-written `{ _variants, _defaultVariants }` object describing the component's variant props — StyleX compiles its styles away, so there is no runtime recipe to derive this from. `lib/generateKnobs.ts` turns each variant into a control: variants whose keys are only `true`/`false` become boolean toggles, everything else becomes a select. Knob values are passed as props straight into the story component. This is why story functions take `Record` and cast to the real prop type. - -- **`lib/registry.ts` is the single source of truth for which components exist**, and they are imported *explicitly* (never `import *`) so sidebar order is deterministic. `getSidebarGroups`, `getModuleBySlug`, and slugging (`lib/slug.ts`, from `meta.title`) read from it. Adding a component touches up to three wiring points: `registry.ts` (sidebar entry + per-page playground lookup), `DocsViewer.tsx`'s `docModules` map (MDX docs), and the hardcoded redirect in `app/page.tsx`. - - ⚠️ **Add each new import and its first usage in the same edit.** The on-save lint-fix (`unused-imports/no-unused-imports` is an `error`) deletes any import that isn't referenced yet, so importing a story export in `registry.ts` (or a component in a `*.stories.tsx`) *before* the code that uses it silently drops the import and you get `X is not defined` at runtime. After wiring, `grep` the new symbol to confirm both the import and its use survived. (Repo-wide footgun; see `clerk-monorepo` skill `references/setup-and-footguns.md`.) - -- **Routing.** Each component is a single page: `/components/[component]` renders its MDX overview via `DocsViewer`. There are no per-story sub-pages — the interactive playground lives *inside* the overview. `app/page.tsx` is a static redirect (currently to `/components/button`) because `registry.ts` eagerly imports story modules (client components / `createContext`), so registry-derived data can't be computed in a Server Component. `DocsViewer` also renders a "View source" link (`ViewSource.tsx`) from `meta.source` — a repo-root-relative path turned into a GitHub URL by `lib/source.ts`. - -- **Shared playground state.** `DocsViewer` wraps each overview in a `PlaygroundProvider` (`PlaygroundContext.tsx`), keyed by slug and seeded from the component's `meta` via `getModuleBySlug`. It owns the knob values (props). The `` and the interactive `` both read/write this single context, so editing a prop in the table updates the preview above it. - -- **Every story renders inside `MosaicProvider`.** `StoryPreview` (the MDX ``) renders a named story with the playground's knob values as props and exposes a Reset button. `StoryEmbed` (the MDX ``) renders a single static variation with default knob values and no controls. - - The header's **RTL** switch (`DirectionToggle`, state in `DirectionProvider`) sets `dir` on ``. It has to sit that high because popover and menu popups portal to ``, so a `dir` on the preview container would never reach them; swingset's own chrome flips along with the story. - -- **The prop table is the knob surface.** `PropTable` (MDX ``) derives rows from `meta.styles._variants`/`_defaultVariants`, then appends the `className` + `style` escape-hatch rows every Mosaic component accepts. Each variant row renders a `KnobControl` in its **Value** column, seeded with the prop's default and bound to the playground context. The escape-hatch rows and `extra` stay static. - -- **MDX.** `mdx-components.tsx` injects custom components into all MDX: `` (→ `StoryPreview`), `` (→ `StoryEmbed`, static), `` (→ interactive `PropTable`), `` (→ `UsageBlock`, a live code snippet that reflects the current knob values), and a `
` override routing fenced code through Shiki (`CodeBlock`). `next.config.mjs` configures `remark-gfm` and `rehype-raw` (with MDX node pass-through) so raw HTML in tables works.
-
-- **`` examples can show their source in a collapsible code footer.** When a story module exposes its own source as `__source` — via a `?raw` self-import (`export { default as __source } from './x.stories?raw'`) — `StoryEmbed` runs `extractStorySource` (`lib/extractStorySource.ts`) to pull the *previewed story function's* source out of that raw text, then `toUsageSnippet` (`lib/exampleSnippet.ts`) to reduce that knob harness to a clean usage snippet (unwraps `export function …() { return (…) }` down to the returned JSX and strips the `{...knobsAsProps(props)}` / `{...props}` knob plumbing), and renders a `CodeFooter` (`CodeFooter.tsx`): a "View code" toggle that's collapsed by default and reveals the snippet with a height animation (Base UI's `--collapsible-panel-height` + `data-starting/ending-style`). It's **opt-in per module** — only modules that export `__source` get a footer, and it's keyed to whichever story `name` the `` renders, so each example shows its own code. Shiki highlighting is shared with the `
`/`CodeBlock` path through the `useShikiHtml` hook. A `` can carry both a code footer and a `composition` footer; they stack under the preview.
-  - The `?raw` query is wired in `next.config.mjs`: an `asset/source` rule handles `?raw` imports, and — crucially — a recursive `excludeRawQuery` pass adds `resourceQuery: { not: [/raw/] }` to every *other* loader so Next's SWC loader doesn't compile the file first (otherwise `__source` would contain `_jsxDEV(…)` output instead of the authored source).
-
-- **Two component layers.** `src/components/ui/*` are shadcn/ui primitives (`components.json`, `base-nova` style, neutral base) used for swingset's *own* chrome (sidebar, tabs, inputs). The components being *documented* come from `@clerk/mosaic`. Don't confuse the two.
-
-## Documenting Mosaic components
-
-The facts above explain *how the app works* and `README.md` covers the *mechanical wiring* of adding a component (registry, `docModules`, redirect). This section is the **house style** — *what to write* once the wiring is in place, so every component page reads consistently.
-
-A component's docs are two files in `src/stories/`:
-
-- `.stories.tsx` — the live demos (named React exports + a `meta`).
-- `.mdx` — the prose page that embeds those demos via the injected MDX tags (``, ``, ``, ``; see the **MDX** architecture note above for the mechanism).
-
-Pick the archetype below by the component's **layer** (its `meta.group`), then follow that archetype's required MDX section order exactly. Same archetype → same headings in the same order, every time. That uniformity is the whole point.
-
-### Layers
-
-`meta.group` places an entry in one of these layers. Group order follows first appearance in the `registry` array. The sidebar sorts `Blocks`, `Components`, `Primitives`, `Styles`, and `Hooks` alphabetically by `title`; `User Button`, `User Profile`, `Reverification`, and `Localization` render in registry order. Within a group, an optional `meta.navigation.category` sub-groups entries under a small collapsible subheading (e.g. `User Profile` splits into `Panels` and `Sections`), collapsed by default unless it contains the active page; category order also follows first appearance in the registry, and uncategorized entries render with no subheading (list them before the categorized ones). Use these exact group strings:
-
-| Group        | What lives here                                                | Archetype |
-| ------------ | -------------------------------------------------------------- | --------- |
-| `User Button` | Composed flow UI (e.g. `UserButton`)                          | C         |
-| `User Profile` | Composed flow UI (e.g. `UserProfileProfilePanel`)             | C         |
-| `Components` | Styled Mosaic components — simple, with a flat variant surface (`Button`, `Input`), or compound (`Card`, `Field`, `Menu`, `Popover`) | A         |
-| `Primitives` | Headless `@clerk/headless` primitives (`Accordion`)            | B         |
-| `Styles`     | Atomic styles that ship as StyleX atoms, not components (`Scroll Area`) | B (adapted) |
-| `Hooks`      | Headless hooks (`useDataTable`)                                | B (adapted) |
-| `Localization` | The `localization` prop on `MosaicProvider`: catalogs, overrides, locale, and the message helpers | B (adapted) |
-
-`User Button` / `User Profile` → `Components` → `Primitives` runs high-level-composition → low-level-primitive. Composed layers are documented as compositions of lower layers (archetype C); leaf layers (Components, Primitives) get full prop/knob docs (archetypes A and B).
-
-`Styles` and `Hooks` are the non-component layers: there is no element to knob, so they follow
-archetype B's shape (Example → Usage → Parts → Styling) with `Props` replaced by whatever the export
-actually surfaces — an argument table for a style function, a return-value table for a hook. A
-`Styles` entry documents the tokens its atoms read, since those tokens _are_ its API; the
-`Hooks` entry (`use-data-table.stories.tsx`) is `meta` alone, with no story exports at all, which is
-the minimum a section entry needs.
-
-Archetype A has two forms, chosen by whether the component exposes a single flat set of variant props: **simple** components (`Button`, `Input`) are knob-driven; **compound** components (`Card`, `Field`, `Menu`, `Popover`) have no flat variant props to knob, so they're documented like a primitive but themed. Both are detailed under Archetype A below.
-
-### `meta` conventions (all archetypes)
-
-```ts
-export const meta: StoryMeta = {
-  group: 'Components', // exact group string from the table
-  title: 'Button', // drives slug + the page 

- label: 'Delete Org', // optional friendlier sidebar text - status: 'stable', // maturity dot in sidebar + page badge; omit to show no status - source: 'packages/mosaic/src/components/button/button.tsx', // repo-root path → "View source" - styles: { - // Hand-written variant surface — archetype A · simple only - _variants: { variant: { primary: {}, outline: {} }, size: { sm: {}, md: {} } }, - _defaultVariants: { variant: 'primary', size: 'md' }, - }, -}; -``` - -- `title` is the component's export name; it produces the slug and is what readers match against code. Set `label` only when the sidebar should read differently (the slug and page heading still come from `title`). -- `status` marks the entry's maturity — `'stable'`, `'wip'`, or `'todo'` (planned, not yet implemented; e.g. `Table`) — rendered as a colored dot (green / amber / gray) to the left of the entry in the sidebar, and as a dot-plus-label badge to the right of the page's `# Title` h1. It is explicit: omitting it displays no status anywhere (not every entry carries one, e.g. `Scroll Area`). A todo entry is `meta` alone plus a one-paragraph MDX page; it skips its archetype's required sections (and `source`) until the component exists. -- `substatus` optionally says *why* a wip entry is wip — one of `'needs wire-up'` (view runs on mock data, not yet connected to its model/controller), `'needs design'`, `'needs a11y'`, `'needs docs'`, `'needs polish'`. It renders in the page badge (`wip: needs wire-up`) and the sidebar dot's tooltip. Most wip entries won't need one; it is ignored on stable entries. -- `source` is always a path **relative to the monorepo root**, pointing at the file that exports the documented component. Always set it — it powers the "View source" link. -- `styles` declares the component's variant props and is **required for archetype A's simple (knob-driven) form** (it generates the knobs and the ``). Keep it in sync with the component's real prop union by hand — StyleX compiles its styles away, so nothing derives it for you. Omit it for compound A components, and for B and C. - -Always import the component explicitly — never `import *`. - -### Archetype A — styled component (`Components`) - -A styled Mosaic component. Which of the two forms below applies is decided by the component's shape, not by preference: if it exposes a single flat set of variant props (declared as `meta.styles`), use the **simple** form; if it's compound — a set of parts with no flat variant props (`Card`, `Field`, `Menu`, `Popover`) — use the **compound** form. - -**Every `Components`-layer story file exposes its source so each `` example renders a code footer.** Add the self-import once, right after the imports: - -```ts -// Exposes this file's own source (via the `?raw` webpack rule) so each `` example -// renders a code footer with its function's source. See `StoryModule.__source`. -export { default as __source } from './.stories?raw'; -``` - -That's all the wiring needed — `StoryEmbed` picks `__source` up automatically and renders a collapsible "View code" footer keyed to each example's story function (see the `` code-footer architecture note above). No MDX change is required; keep authoring `` as before. This applies to both A forms (simple and compound) and to every example a Components page ships. - -#### A · simple — flat variant props (`Button`, `Input`) - -Has a declared variant surface, so the page is **knob-driven**: an interactive canvas plus a generated prop table. Required MDX section order: - -```mdx -import * as ButtonStories from './button.stories'; - -# Button - - - -## Playground - - - -## Props - - - -## Usage - - - Click me - - ---- - -## Examples - -### Sizes - - - -### Disabled - - -``` - -- **Playground / Props / Usage are mandatory and always in this order.** The three share one playground state: editing a row in `` re-renders `` above it and regenerates the `` snippet below it. -- The story file exports a primary demo (rendered by ``) plus one named export per variation under **Examples**. Each story takes `props: Record` and casts through a local `knobsAsProps` helper — knobs are dynamically typed, the component isn't. -- Use ``'s `extra` for documenting non-variant props; the `className` + `style` escape-hatch rows are appended for you. -- Use `` to pin static, non-knob props in the generated snippet. -- `` renders `Prop | Type | Default | Value`: the **Default** column is filled from `meta.styles._defaultVariants`, and the **Value** column is the live knob seeded with that default. No per-row default annotation is needed; see _Document the default value_ under Archetype B. - -#### A · compound — parts, no flat variant props (`Card`, `Field`, `Menu`, `Popover`) - -A compound styled component (`Popover.Root`/`Popover.Popup`/…) has no single flat prop interface to knob, so there's no `` or ``. Document it like a primitive (archetype B) but themed — the difference is the **Styling** section, which lists each part's `.cl-` class and `data-` attributes rather than saying "bring your own CSS". Required MDX section order: - -```mdx -import * as PopoverStories from './popover.component.stories'; - -# Popover - - - -## Example - - - -## Usage - - - -## Parts - - - -## Styling - - -``` - -The story is `meta` (no `styles`) plus a single `Default` export that renders the composed parts. The file pair is named `.component.stories.tsx` / `.component.mdx` so it doesn't collide with the headless `Primitives` entry of the same title (e.g. `Menu`, `Popover` exist in both layers); the `docModules` map disambiguates by group. - -### Archetype B — headless primitive (`Primitives`) - -No styles, so there's no knob canvas. The single demo renders the primitive **raw (unstyled)** to show only behavior, state, and ARIA wiring. The prop/styling tables are **hand-written** (there is no variant surface to derive them from). Required MDX section order: - -```mdx -# Accordion - - - -## Example - - - -## Usage - - - -## Parts - - - -## Props - - - -## Styling - - -``` - -The story is `meta` (no `styles`) plus a single `Default` export that renders the primitive unstyled. Don't add a `` or `` — primitives have neither knobs nor a variant surface to drive them. - -**Document the default value for every prop in a dedicated Default column.** Every props table — auto and hand-written — has a **Default** column; the `Type` stays a plain union/enum and the default is named in its own column (the convention every component-doc site and TypeDoc's `@default` tag follow), never inlined into the type. The auto `` renders `Prop | Type | Default | Value` and fills Default from `meta.styles._defaultVariants` (the **Value** column is the live knob seeded with that default); hand-written tables render `Prop | Type | Default | Description` and fill it by hand. Name the default member (`'base'`, `'multiple'`, `'bottom-start'`); use `—` when there is no default (a controlled-only or required prop) and append `(required)` for required props; when the default is behavioral rather than a literal, state it in words (`inherits Root`, `falls back to value`). - -### Archetype C — composed layer (`User Button`, `User Profile`) - -These compose lower layers, so the docs lead with the composition rather than knobs. Required MDX: - -```mdx -import * as UserButtonStories from './user-button.stories'; - -# UserButton - - - - -``` - -- The `composition` array names each lower-layer component this one builds on, with a link to that component's page and its `layer`. List every direct dependency so the layering is navigable. -- The story is `meta` plus a single `Default` export that renders the composed UI with no knobs. - -### Writing the prose - -- Keep the intro to one short, present-tense paragraph: what the thing is and what it's for. For primitives, say explicitly that it's headless and ships no styles. -- Prose should add what a demo can't — behavior, accessibility, when to reach for it — not restate prop names already in the table. -- Describe the API as it is; leave the reasoning that produced it out. No "not a `size`, because…", no rejected alternatives, no history of what a prop used to be — that belongs in a code comment or the PR. The docs get the conclusion, stated plainly and briefly. -- Lead every page with the heading hierarchy its archetype prescribes; don't invent new top-level sections or reorder them. Consistency across pages is the goal. - -### Before you finish - -- [ ] `meta.source` is set to a repo-root-relative path. -- [ ] Story renders. -- [ ] `Components`-layer story files export `__source` (the `?raw` self-import) so every `` example gets a "View code" footer. -- [ ] MDX sections match the archetype's required order exactly. -- [ ] Every props-table row states its default in the **Default** column (auto `` fills it from `meta.styles._defaultVariants`; `—` / `(required)` when none). -- [ ] Wiring done per `README.md`: `registry.ts`, `DocsViewer.tsx`'s `docModules`, and the `app/page.tsx` redirect if this is now the first component. -- [ ] `pnpm format --filter @clerk/swingset` is clean.