Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/mighty-jars-sell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
7 changes: 4 additions & 3 deletions .claude/skills/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Skills are Claude Code specific. Cursor does not read this directory; it uses `.

## Skills in this repo

| Skill | Use it for |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `clerk-monorepo` | Day-to-day work in the monorepo: setup, build/test loops, the package map, changesets, commits, PRs, breaking-change checks. |
| Skill | Use it for |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `clerk-monorepo` | Day-to-day work in the monorepo: setup, build/test loops, the package map, changesets, commits, PRs, breaking-change checks. |
| `mosaic` | Mosaic flow UI: authoring machines, controllers, and views, and migrating a legacy component into the split (with parity verification). |
218 changes: 0 additions & 218 deletions .claude/skills/mosaic-machine/SKILL.md

This file was deleted.

57 changes: 57 additions & 0 deletions .claude/skills/mosaic/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
name: mosaic
description: >-
Work on Mosaic UI: styling a component with slot recipes (`defineSlotRecipe` /
`useRecipe` / slots / variants), or building a flow — authoring a state machine
(`setup`, states/guards/`invoke`, wiring to React with `useMachine`/`useActor`/
`useSelector`), writing the controller (Clerk adapter) or view (rendering) layer,
testing any of those layers, or migrating a legacy / pre-Mosaic component into the
machine / controller / view split. Use when building, styling, debugging, testing, or
migrating anything Mosaic. `references/mosaic-architecture.md` (repo root) holds the
design-system contract; this skill is the how-to layer.
---

# Mosaic UI

Two things live under Mosaic, and this skill covers the how-to for both:

- **Styled components** are authored with **slot recipes** — one recipe owns a
part's slot identity (`data-cl-slot`), variants, state, and appearance
cascade; `useRecipe` resolves it and hands back per-slot props to spread.
- **Flows** follow a **machine → controller → view** split that keeps Clerk
resource logic out of visual components and makes behavior testable without a
running Clerk app:

```text
machine Pure flow rules: states, events, guards, async invokes, errors.
No React hooks. No Clerk hooks. No Clerk resource objects.

controller Clerk/data adapter: reads Clerk hooks/resources, injects async
effects into machine context, gates permissions, derives view props.
The only layer that may import Clerk hooks or call resource methods.

view Rendering only: receives a snapshot plus explicit props, renders UI,
sends events. No Clerk imports. No data-fetching. No mutations.
```

`references/mosaic-architecture.md` (repo root, read by all agents) is the
canonical contract for the whole design system — tokens, theme delivery, the
`data-cl-*` styling API, slot recipes, appearance/cascade/scope, and the "Flow
and data architecture" section that defines the split. Read it for the _what_;
this skill is the _how-to_.

## Which reference to read

| You are… | Read |
| ------------------------------------------------------------------ | ------------------------------------------------------ |
| Styling a component (slot recipes, `useRecipe`, variants, slots) | `references/styling.md` |
| Authoring or debugging a state machine, or wiring one to React | `references/machines.md` → in-tree `machine/README.md` |
| Writing the controller (Clerk adapter, permissions, revalidate) | `references/controllers.md` |
| Writing the view (rendering a snapshot, sending events) | `references/views.md` |
| Testing a machine, controller, or view | `references/testing.md` |
| Migrating a legacy component into Mosaic (the end-to-end workflow) | `references/migration.md` |
| Running the parity audit that guards a migration | `references/parity-audit.md` |

The migration workflow (`migration.md`) ties the flow references together: it
treats the legacy component as the spec and drives you through the machine,
controller, and view layers, then verifies parity with `parity-audit.md`.
61 changes: 61 additions & 0 deletions .claude/skills/mosaic/references/controllers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Controllers

The controller is the adapter from Clerk resources into machine context and view
props. It is the **only layer in a Mosaic flow that may import Clerk hooks or
call Clerk resource methods** — the machine (`machines.md`) and view
(`views.md`) stay Clerk-free so they remain testable in plain JS.

See `references/mosaic-architecture.md` → "Controllers" for the canonical
example. This file is the practical checklist.

## Responsibilities

- **Read Clerk state.** Call hooks like `useOrganization()`, `useUser()`,
`useSession()`. This is the only layer allowed to.
- **Inject async effects + live props into machine context.** Pass plain
functions that close over live resources; `useMachine` re-seats context via
`useLayoutEffect` every render, so the machine always reads the latest prop.

```tsx
const [snapshot, send, actor] = useMachine(deleteOrgMachine, {
context: {
organizationName: organization?.name ?? '',
destroyOrganization: () => organization?.destroy() ?? Promise.resolve(),
},
});
```

- **Gate permissions and visibility.** Resolve `session.checkAuthorization(...)`
and collapse loading/permission/empty into an explicit status the wrapper
branches on — not scattered booleans:

```tsx
if (!canRead || !settings.enabled || !organization) return { status: 'hidden' as const };
if (!firstPageLoaded) return { status: 'loading' as const };
return { status: 'ready' as const, snapshot, send, canSubmit: actor.can({ type: 'CONFIRM' }) };
```

- **Own revalidate timing.** Call `data.revalidate()` / `.reload()` after
mutations. Deciding _when_ (fire-and-forget vs awaited) is controller logic,
not view logic.
- **Handle first-page-load empty-state.** Wait for the first page before
deciding to hide a section, so read-only users don't see a hide-then-show
flicker.
- **Derive view props.** Expose `actor.can(...)` results (e.g. `canSubmit`) so
the view never re-implements a machine guard.

## Rules

- Pass **plain data and plain functions** into machines and views. Do **not**
pass Clerk resource objects through to the view.
- Branch the wrapper on the controller's `status`, not on raw `isLoaded` flags
sprinkled through the tree.

## Testing

Test the controller against a **mocked Clerk** for the gating / `hidden` /
empty-state logic. This is the **highest-risk, least-covered layer**: it holds
the Clerk resource semantics that the pure machine tests can't reach. When a
migration loses behavior, it is usually a controller responsibility (revalidate
timing, a permission gate, an empty-state rule) that quietly went missing —
concentrate scrutiny here.
18 changes: 18 additions & 0 deletions .claude/skills/mosaic/references/machines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Machines

The machine runtime is documented **next to the code**, and that in-tree doc is
the source of truth (it's updated in the same diff when the runtime changes and
is readable by every tool, not just Claude Code). Read:

- **`packages/ui/src/mosaic/machine/README.md`** — the mental model (state /
event / context / transition), your first machine, `setup` to drop the type
boilerplate, running it with `createActor` / `useMachine`, and the API at a
glance (`assign`, `invoke`, `guard`, `always`, `entry`/`exit`, `final`,
`mockActor`, `useActor`, `useSelector`, `recheck()`).
- **`packages/ui/src/mosaic/machine/ADOPTION.md`** — when a flow is worth a
machine and when it isn't, with real before/after migrations.

The machine is the pure flow layer: states, events, guards, async `invoke`,
errors — no React hooks, no Clerk. To wire it to Clerk data see `controllers.md`;
to render its snapshot see `views.md`; to test it see `testing.md`; to migrate a
legacy component into this pattern see `migration.md`.
Loading
Loading