diff --git a/.changeset/fix-stale-agent-docs.md b/.changeset/fix-stale-agent-docs.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/fix-stale-agent-docs.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.claude/skills/clerk-monorepo/SKILL.md b/.claude/skills/clerk-monorepo/SKILL.md index e5dc8039a59..0ca2d8c0ddf 100644 --- a/.claude/skills/clerk-monorepo/SKILL.md +++ b/.claude/skills/clerk-monorepo/SKILL.md @@ -11,8 +11,7 @@ description: >- # Working in the clerk/javascript monorepo -This is Clerk's JavaScript SDK monorepo: 24 packages (22 published `@clerk/*` plus the private -`@clerk/msw` and `@clerk/swingset`) managed with pnpm +This is Clerk's JavaScript SDK monorepo: the `@clerk/*` packages under `packages/`, managed with pnpm workspaces and Turborepo. Read this before building, testing, committing, or touching anything under `packages/`. @@ -27,7 +26,7 @@ The order matters more than the commands. Do them in sequence: 1. **Node `>=24.15.0`** (pinned in `.nvmrc`). `nvm use` if you have nvm. Wrong Node version is the single most common cause of cryptic "cannot find module @clerk/..." build errors. 2. **`corepack enable`** before installing. The `preinstall` hook runs `only-allow pnpm`; npm/yarn - are hard-blocked. Corepack pins the right pnpm (`>=10.33.0`). + are hard-blocked. Corepack pins the right pnpm (`engines` in `package.json`). 3. **`pnpm install` from the repo root** (never a subdirectory). It is a workspace; installing from a package dir leaves cross-package links broken. 4. **`pnpm build`** before anything else. Packages depend on each other's built `dist/` + `.d.ts`. @@ -39,7 +38,7 @@ Full sequence, the 11 footguns, and the internal integration-test / 1Password se ## Package map: where does X live? -The ~10 packages people touch most. Full 25-package table, the dependency pyramid, and the complete +The ~10 packages people touch most. How to list every package, the dependency pyramid, and the complete "change X, touch Y" routing are in [`references/package-map.md`](references/package-map.md). | Package | You change it when... | @@ -143,8 +142,8 @@ see [`references/setup-and-footguns.md`](references/setup-and-footguns.md) and ` Each rule below restates `AGENTS.md`; the parenthetical is how it is enforced. -- **pnpm only, Node `>=24.15`, pnpm `>=10.33`.** (`preinstall` blocks npm/yarn; `engines` in - `package.json`.) +- **pnpm only, at the Node and pnpm versions in `engines` in `package.json`.** (`preinstall` blocks + npm/yarn.) - **Every PR needs a changeset.** Write the file directly; the `pnpm changeset` / `pnpm changeset:empty` scripts are interactive prompts an agent cannot drive. A changeset is a changelog entry for users upgrading, not a summary of the diff. See diff --git a/.claude/skills/clerk-monorepo/references/package-map.md b/.claude/skills/clerk-monorepo/references/package-map.md index 47abcecfa41..ec3315f1ae8 100644 --- a/.claude/skills/clerk-monorepo/references/package-map.md +++ b/.claude/skills/clerk-monorepo/references/package-map.md @@ -1,48 +1,25 @@ # Package map -The 25 active, git-tracked packages, the dependency shape, and the full "change X, touch Y" routing. -`SKILL.md` has the short version (the ~10 packages people touch most). - -> The authoritative package list is the set of `packages/*/package.json` files tracked in git. The -> `packages/` directory can also hold leftover build artifacts from **removed** packages (e.g. -> `types`, `remix`, `themes`, `elements`, `agent-toolkit`, `dev-cli`) that contain only build output -> (`dist/`, `node_modules/`, `.turbo/`) and no `package.json`. Those are not active packages and are -> not valid commit scopes. +Every package, the dependency shape, and the full "change X, touch Y" routing. `SKILL.md` has the +short version (the ~10 packages people touch most). ## All packages -Categories: **foundational** (depended on by most others), **browser-runtime** (ships to the -browser directly), **adapter** (framework SDK), **ui/i18n**, **tooling**. The "BC" column marks the -two packages whose runtime is pushed into apps pinned to older SDKs, so they carry the strict -backwards-compatibility contract (see `breaking-changes.md`). +The package list is the set of git-tracked `packages/*/package.json` files. Most have a +`description` saying what the package is, and `"private": true` marks the ones that never publish: + +```bash +for f in $(git ls-files 'packages/*/package.json' | awk -F/ 'NF==3'); do + node -p "const p = require('./$f'); p.name + (p.private ? ' (private)' : '') + ': ' + (p.description ?? '')" +done +``` + +The `packages/` directory can also hold leftover build artifacts from **removed** packages (e.g. +`types`, `remix`, `themes`, `elements`) with no `package.json`. Those are not active packages and are +not valid commit scopes. -| Package | Category | BC | Purpose | -| ------------------------------- | --------------- | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `@clerk/shared` | foundational | | Internal utilities used by all SDKs (storage, events, React helpers). Hosts the shared types as `@clerk/shared/types`. Most-depended-on package. | -| `@clerk/backend` | foundational | | Backend API REST client, JWT verification, webhook helpers. Used by every server adapter. | -| `@clerk/clerk-js` | browser-runtime | ⚠️ | The browser runtime (script tag). Backwards-compat sensitive. | -| `@clerk/ui` | ui | ⚠️ | React components for the hosted sign-in / sign-up flows (`packages/ui/src/components`). Consumed by the react/astro/vue/chrome-extension adapters. Backwards-compat sensitive. | -| `@clerk/mosaic` | ui | | Experimental next-generation React components. Public ESM package (`UserButton` + `styles.css`). Ships unstyled, accessible primitives (dialog, menu, popover, ...) internally under `src/primitives/`. Reads Clerk context from the host SDK via `@clerk/shared`. | -| `@clerk/react` | adapter (core) | | React hooks and context (`useAuth`, `useUser`, `useOrganization`, ...). Shared by the React-based adapters. | -| `@clerk/nextjs` | adapter | | Next.js SDK: middleware, route handlers, server components. | -| `@clerk/express` | adapter | | Express middleware and server helpers. | -| `@clerk/fastify` | adapter | | Fastify plugin. | -| `@clerk/hono` | adapter | | Hono SDK (edge / serverless). | -| `@clerk/astro` | adapter | | Astro integration (components + server utilities). | -| `@clerk/nuxt` | adapter | | Nuxt module (Vue). | -| `@clerk/vue` | adapter | | Vue 3 composables and components. | -| `@clerk/react-router` | adapter | | React Router v7 SDK. | -| `@clerk/tanstack-react-start` | adapter | | TanStack React Start SDK. | -| `@clerk/expo` | adapter | | React Native / Expo SDK. | -| `@clerk/expo-native-components` | adapter | | Native components (`AuthView`, `UserButton`, `UserProfileView`) and the native module for Expo, built on clerk-ios / clerk-android. Optional companion to `@clerk/expo`. | -| `@clerk/expo-passkeys` | adapter | | Passkeys companion library for Expo. | -| `@clerk/chrome-extension` | browser-runtime | | SDK for Chrome extension contexts. | -| `@clerk/localizations` | ui/i18n | | Translation strings for the UI components. Consumed by `ui`. | -| `@clerk/testing` | tooling | | E2E test helpers for consumers (Playwright + Cypress). | -| `@clerk/msw` | tooling | | MSW request handlers for mocking the Clerk API in tests. Private (not published). | -| `@clerk/swingset` | tooling | | Component explorer for `@clerk/mosaic`. Private (not published). | -| `@clerk/upgrade` | tooling | | CLI codemod tool for upgrading consumers between SDK versions. | -| `@clerk/eslint-plugin` | tooling | | ESLint plugin enforcing Clerk patterns across JavaScript frameworks (lint rules shipped to apps). Published. | +Only `@clerk/clerk-js` and `@clerk/ui` carry the strict backwards-compatibility contract: their +runtime is pushed into apps pinned to older SDKs (see `breaking-changes.md`). Tests: `pnpm turbo test --filter=@clerk/` (or `pnpm --filter @clerk/ test` after a build). Most packages use vitest; `@clerk/backend` runs a multi-runtime suite (node + edge + diff --git a/.claude/skills/clerk-monorepo/references/setup-and-footguns.md b/.claude/skills/clerk-monorepo/references/setup-and-footguns.md index a4a1a73f010..9f330f93904 100644 --- a/.claude/skills/clerk-monorepo/references/setup-and-footguns.md +++ b/.claude/skills/clerk-monorepo/references/setup-and-footguns.md @@ -12,7 +12,7 @@ integration-test setup. The short happy path is in `SKILL.md`; this is the full `Cannot find module '@clerk/shared'` or missing-type errors downstream. 2. **`corepack enable`** before installing. `package.json` has `preinstall: only-allow pnpm`, so npm and yarn are blocked outright. Corepack reads `packageManager` and uses the pinned pnpm - (`>=10.33.0`). Without it you may run a mismatched global pnpm. + (`engines` in `package.json`). Without it you may run a mismatched global pnpm. 3. **`git clone` and `cd` into the repo root.** 4. **`pnpm install` from the root.** This is a pnpm workspace; the root install links all packages together. Installing from a package subdirectory leaves workspace links broken. First install diff --git a/.cursor/commands/cmt.md b/.cursor/commands/cmt.md index f2905cca9a9..ead3f2d67bc 100644 --- a/.cursor/commands/cmt.md +++ b/.cursor/commands/cmt.md @@ -1,5 +1,5 @@ --- -description: v7 - Generate commit message from chat changes +description: v8 - Generate commit message from chat changes (note to LLMs: increment version when you make changes to this file) --- @@ -9,16 +9,11 @@ Generate a commit message for changes in this chat. **Do not commit or push** (s --- -## ⚙️ REPO-SPECIFIC CONFIGURATION +## Scopes and types -> **Edit this section when using in a new repository.** +The scope is required. Valid scopes: any `packages/*` short name, the same name with `clerk-` removed (`clerk-js` or `js`), or `repo`, `release`, `e2e`, `ci`, `*`. Source of truth: `commitlint.config.ts`. -### Valid Scopes - -Scopes must match package/app names. No scope is also valid. Invalid scope = commitlint rejection. - -- **Packages:** astro, backend, chrome-extension, clerk-js, elements, expo, expo-passkeys, express, fastify, localizations, nextjs, nuxt, react, react-router, remix, shared, tanstack-react-start, testing, themes, types, ui, upgrade, vue -- **Other:** docs, repo, release, e2e, \* +Only the PR title is linted, and it becomes the squash commit, so the title format matters most. --- @@ -33,31 +28,17 @@ Scopes must match package/app names. No scope is also valid. Invalid scope = com ## Valid Types -**Types (REQUIRED - ALWAYS FIRST):** feat | fix | build | chore | ci | perf | refactor | revert | style | test - -## FORMAT - READ THIS CAREFULLY - -**CRITICAL: Type is MANDATORY and ALWAYS comes FIRST** - -``` -type(scope): description ← CORRECT: type first, scope optional -type: description ← CORRECT: type without scope -``` +feat | fix | docs | build | chore | ci | perf | refactor | revert | style | test -**WRONG FORMATS:** +## Format ``` -docs: description ← WRONG: "docs" is a SCOPE, not a type! -scope: description ← WRONG: missing type! +type(scope): description ← CORRECT +type: description ← WRONG: scope is required +scope: description ← WRONG: missing type ``` -**If working with docs:** - -``` -feat(docs): add new guide ← CORRECT -chore(docs): update readme ← CORRECT -docs: something ← WRONG -``` +`docs` is a type, not a scope: `docs(repo): update contributing guide`. ## Process @@ -75,7 +56,7 @@ docs: something ← WRONG Then **STOP and wait** for response. -6. Generate commit message (TYPE FIRST, then optional scope in parentheses) +6. Generate commit message (type first, then the scope in parentheses) 7. If "stage" present → run `git add` on affected files (only chat-mentioned files unless "all" flag present) ## Format Rules @@ -84,9 +65,8 @@ docs: something ← WRONG type(scope): description ``` -- **TYPE IS REQUIRED** - one of: feat, fix, build, chore, ci, perf, refactor, revert, style, test -- **SCOPE IS OPTIONAL** - if present, wrap in parentheses after type -- All lowercase, ≤72 chars +- Type and scope are both required +- ≤72 chars - Title = "what it does", body = "why" - Be specific (use domain terms from conversation, not generic words) - No filler: avoid "update", "changes", "code" when meaningless @@ -121,7 +101,7 @@ Why: ## Examples ``` -feat(api): add rate limiting to auth endpoints +feat(backend): add rate limiting to auth endpoints Why: DDoS attacks on /api/sign-in caused production degradation. @@ -136,21 +116,21 @@ May need IP allowlist for trusted services (CLERK-5678). ``` ``` -chore(docs): fix typo in authentication guide +docs(nextjs): fix typo in the middleware README Why: Users reported confusion from misspelling. ``` ``` -ci: consolidate test job into checks to speed up pipeline +ci(repo): consolidate test job into checks to speed up pipeline Why: Reduce CI overhead by running tests in same job as lint/type checks. ``` ``` -feat(docs): document commitlint scope validation system +docs(repo): document commitlint scope validation system Why: Added clear documentation to help contributors understand scope requirements. @@ -159,9 +139,9 @@ Added clear documentation to help contributors understand scope requirements. ## Remember - Important:Do not add unnecessary new lines or paragraphs to sentences. Let the editor wrap lines as needed. -- **TYPE FIRST, ALWAYS** - never start with a scope +- **TYPE FIRST, ALWAYS**, then the scope in parentheses - **NEVER COMMIT OR PUSH** -- Type is REQUIRED, scope is optional +- Type and scope are both required - **Always explain WHY changes were made** - what problem was being solved, what issues were identified, what motivated the change - **Provide enough context** - someone reading the commit in 2 years should understand the reasoning without access to the conversation - Only ask questions for high-impact changes diff --git a/.cursor/rules/clerk-js-ui.mdc b/.cursor/rules/clerk-js-ui.mdc index 63fc16e568e..9125559f75f 100644 --- a/.cursor/rules/clerk-js-ui.mdc +++ b/.cursor/rules/clerk-js-ui.mdc @@ -164,15 +164,10 @@ const errorMessage = translateError(apiError); ### Card Pattern ```tsx const MyCard = () => ( - - - - Title - - Content - Footer - - + + Content + Footer + ); ``` diff --git a/AGENTS.md b/AGENTS.md index c2f4c74b12c..447a0ccb8b6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,8 +7,8 @@ Clerk's JavaScript SDK and library monorepo. - Non-major releases in `packages/clerk-js` and `packages/ui` are pushed out to consuming applications without requiring explicit package updates. This means a new `clerk-js` runtime can load into an app pinned to an older `@clerk/nextjs` (or any other framework SDK) version, so changes must remain backwards-compatible with SDK versions already in the wild, not just the current monorepo state. Removing or renaming anything an older SDK still calls will break production for those users. Extra care must be put into any changes to these packages. - The API exposed from the core Clerk class in `packages/clerk-js/src/core/clerk.ts` is a contract that is depended on by internal and external consumers (including older SDK versions still loading the latest `clerk-js`). Changes to this API must be done in a major version to avoid breakage. - Unless specifically instructed to do so by the user, _do not write any code comments_. The only permissible time to edit existing comments is when the behavior they describe has been modified in such a way that the comment is no longer accurate. When another rule instructs you to write a JSDoc comment, specifically call out the content of the comment to the user for review. -- Use `pnpm` only. `npm` and `yarn` are blocked by `preinstall`. Node `>=24.15`, pnpm `>=10.33`. -- Every PR needs a changeset. `pnpm changeset` for package changes, `pnpm changeset:empty` for tooling/repo-only. Empty changesets are two `---` delimiters with no body. A changeset is a changelog entry for users upgrading the package, not a summary of the work done in the PR. Describe the user-facing change (what changed for someone consuming the library and how it affects them) rather than the implementation details of the diff. If a change has no user-facing impact, use an empty changeset. +- Use `pnpm` only. `npm` and `yarn` are blocked by `preinstall`. Node `>=24.15`, pnpm `>=10.34.5`. +- Every PR needs a changeset. Agents write `.changeset/.md` directly, because `pnpm changeset` prompts interactively. Empty changesets, for tooling/repo-only changes, are two `---` delimiters with no body. A changeset is a changelog entry for users upgrading the package, not a summary of the work done in the PR. Describe the user-facing change (what changed for someone consuming the library and how it affects them) rather than the implementation details of the diff. If a change has no user-facing impact, use an empty changeset. - Reuse existing patterns before writing new ones. Before adding a helper, hook, or a local way of handling errors, pending state, focus or similar cross-cutting concerns, search the package for one that already solves it. If none fits, stop and ask the user before introducing a new pattern: say what the problem is, what you checked, and what you propose. - PR descriptions follow `.github/PULL_REQUEST_TEMPLATE.md` and add no sections of their own. Never add a "Testing" (or "Test plan" / "How to test") section summarizing the tests written or the checks run; the Checklist covers that and reviewers read the diff. Describe the change, not the work done on it. diff --git a/packages/mosaic/docs/migration.md b/packages/mosaic/docs/migration.md index ac084d19997..83e46a9edbc 100644 --- a/packages/mosaic/docs/migration.md +++ b/packages/mosaic/docs/migration.md @@ -3,8 +3,8 @@ Migrating a legacy component means taking logic that was fused into one file and pulling it apart into the model / controller / view layers (see the skill overview and `packages/mosaic/ARCHITECTURE.md` → "Flow and data architecture"). -`packages/mosaic/src/features/user-button/` is the fullest worked example of the -finished shape. +`packages/mosaic/src/features/reverification/` is a compact worked example of the +finished shape: model, controller, view, types, messages, wrapper, and a feature test. **The core risk this workflow exists to manage:** a legacy component fuses rendering, data-fetching, and flow logic into one blob. Splitting it three ways diff --git a/packages/swingset/CLAUDE.md b/packages/swingset/CLAUDE.md index fff87489c40..5475c0887dc 100644 --- a/packages/swingset/CLAUDE.md +++ b/packages/swingset/CLAUDE.md @@ -22,28 +22,28 @@ Swingset has no tests, and none should be added. Don't add test files, a Vitest 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. +- **Consumes Mosaic from source, not build.** `@clerk/mosaic` is aliased to `../mosaic/src` in _three_ places that must stay in sync: `tsconfig.json` (`paths`), and in `next.config.mjs` both the webpack `resolve.alias` and the StyleX plugin's `aliases`. 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. +- **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. `src/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/(explorer)/page.tsx`. +- **`src/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`, `getModule(groupSlug, componentSlug)`, and slugging (`src/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/(explorer)/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.** Route groups split the app: `(explorer)` is the component playground, `(clerk)` is the Live Sandbox (`/live`, `/sign-in`, `/sign-up`) and needs keys from `packages/swingset/.env.local`. 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/(explorer)/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`. +- **Routing.** Route groups split the app: `(explorer)` is the component playground, `(clerk)` is the Live Sandbox (`/live`, `/sign-in`, `/sign-up`) and needs keys from `packages/swingset/.env.local`. Each component is a single page: `(explorer)/[group]/[component]` renders its MDX overview via `DocsViewer`. There are no per-story sub-pages — the interactive playground lives _inside_ the overview. `app/(explorer)/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 `src/lib/source.ts`. -- **Live Sandbox pages.** Every `app/(clerk)/live/*/page.tsx` opens with `LivePageHeader` (`app/(clerk)/live-page-header.tsx`), which renders the page's `h1` and, beside it, the entry's status badge (`StatusBadge`) looked up from the live route map in `lib/live-navigation.ts`. The badge belongs in the title row, where the reader is looking, mirroring the docs pages' `DocTitle` — never in the sticky header chrome. Don't hand-write an `h1` on a live page; pass the title and description to `LivePageHeader` so the badge stays attached. The live sidebar is also the inventory for the User Profile and Organization Profile groups: every Panels/Sections entry from the registry appears there, and one without a route in `liveRoutes` renders as a gray `todo` placeholder (`live/todo/[group]/[component]`, a dashed gray box labelled TODO). When you add a live harness for one, add its route to `liveRoutes` and the placeholder disappears. +- **Live Sandbox pages.** Every `app/(clerk)/live/*/page.tsx` opens with `LivePageHeader` (`app/(clerk)/live-page-header.tsx`), which renders the page's `h1` and, beside it, the entry's status badge (`StatusBadge`) looked up from `livePages` in `src/lib/live-navigation.ts`. The badge belongs in the title row, where the reader is looking, mirroring the docs pages' `DocTitle` — never in the sticky header chrome. Don't hand-write an `h1` on a live page; pass the title and description to `LivePageHeader` so the badge stays attached. The live sidebar is also the inventory for the User Profile and Organization Profile groups: every Panels/Sections entry from the registry appears there, and one without a route in `livePages` renders as a gray `todo` placeholder (`live/todo/[group]/[component]`, a dashed gray box labelled TODO). When you add a live harness for one, add its route to `livePages` and the placeholder disappears. -- **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. +- **Shared playground state.** `DocsViewer` wraps each overview in a `PlaygroundProvider` (`PlaygroundContext.tsx`), keyed by slug and seeded from the component's `meta` via `getModule`. 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 header's **Chaos** switch (`ChaosToggle`, state in `ChaosProvider`) stress-tests layouts with worst-case data that still reads as real: long names, companies, emails and places across scripts and RTL, plus long row counts. `StoryMosaicProvider` (used by both story renderers) remounts the story on toggle so fixture state reseeds. UI copy stays the built-in English. Fixtures opt their seed data in with `useChaosFixture(data, toChaos)`, building the chaos version from the helpers in `lib/chaos.ts`: seeded `@faker-js/faker` generators (`chaosName`, `chaosPerson`, `chaosUsername`, `chaosEmail`, `chaosCompany`, `chaosSlug`, `chaosPhone`, `chaosPlace`), which take the longest of several samples across locales, plus `chaosRole`, `chaosPick` for hand-written lists faker can't supply, and `chaosRows`. Never stretch copy with filler; pick a longer value someone would actually type. When you add a fixture, wrap its seed data the same way. + - The header's **Chaos** switch (`ChaosToggle`, state in `ChaosProvider`) stress-tests layouts with worst-case data that still reads as real: long names, companies, emails and places across scripts and RTL, plus long row counts. `StoryMosaicProvider` (used by both story renderers) remounts the story on toggle so fixture state reseeds. UI copy stays the built-in English. Fixtures opt their seed data in with `useChaosFixture(data, toChaos)`, building the chaos version from the helpers in `src/lib/chaos.ts`. Never stretch copy with filler; pick a longer value someone would actually type. When you add a fixture, wrap its seed data the same way. -- **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. +- **The prop table is the knob surface.** `PropTable` (MDX ``) derives rows from `meta.styles._variants`/`_defaultVariants`, then appends the `xstyle` escape-hatch row 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 `xstyle` row 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.
+- **`` 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` (`src/lib/extractStorySource.ts`) to pull the _previewed story function's_ source out of that raw text, then `toUsageSnippet` (`src/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.
@@ -61,12 +61,11 @@ Pick the archetype below by the component's **layer** (its `meta.group`), then f
 
 ### 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`, `Organization Switcher`, `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:
+`meta.group` places an entry in one of these layers. Group order follows first appearance in the `registry` array. Groups in `ALPHABETICAL_GROUPS` (`src/lib/registry.ts`) sort their entries by `title`; the rest 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           |
-| `Organization Switcher` | Composed flow UI (e.g. `OrganizationSwitcher`)                                                                                       | 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 primitives from `@clerk/mosaic`'s `src/primitives/` (`Accordion`)                                                           | B           |
@@ -104,7 +103,7 @@ export const meta: StoryMeta = {
 
 - `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 reverification'` (wired to FAPI, but a sensitive action still runs without session reverification), `'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.
+- `substatus` optionally says _why_ a wip entry is wip — one of the `WipSubstatus` values in `src/lib/types.ts`. 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.
 
@@ -117,8 +116,6 @@ A styled Mosaic component. Which of the two forms below applies is decided by th
 **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';
 ```
 
@@ -176,7 +173,7 @@ import * as ButtonStories from './button.stories';
 
 - **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 ``'s `extra` for documenting non-variant props; the `xstyle` escape-hatch row is 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.
 
@@ -284,7 +281,7 @@ import * as SwitcherStories from './user-button.stories';
 
 - 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.
+- 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 the PR description. 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
diff --git a/packages/swingset/README.md b/packages/swingset/README.md
index 8f7cb7a0000..45b66124fed 100644
--- a/packages/swingset/README.md
+++ b/packages/swingset/README.md
@@ -51,7 +51,7 @@ const myModule: StoryModule = { meta: myMeta, Default };
 export const registry: StoryModule[] = [..., myModule];
 ```
 
-Import stories explicitly (not `import *`) and add the module under its group's comment in the `registry` array. The sidebar sorts `Blocks`, `Components`, `Primitives`, `Styles`, and `Hooks` alphabetically by `title`; `User Button`, `User Profile`, and `Reverification` render in array order.
+Import stories explicitly (not `import *`) and add the module under its group's comment in the `registry` array. The sidebar sorts `Blocks`, `Components`, `Primitives`, `Styles`, and `Hooks` alphabetically by `title`; the other groups render in array order (see `ALPHABETICAL_GROUPS` in `src/lib/registry.ts`).
 
 **3. Add docs** — `src/stories/my-component.mdx`
 
diff --git a/references/theming-architecture.md b/references/theming-architecture.md
index b4170d91ae1..c6f5178a201 100644
--- a/references/theming-architecture.md
+++ b/references/theming-architecture.md
@@ -12,7 +12,7 @@ Users pass an `appearance` prop to `` (global) or directly to ind
 
 ```tsx
 import { ClerkProvider } from '@clerk/nextjs';
-import { dark } from '@clerk/themes';
+import { dark } from '@clerk/ui/themes';