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/mosaic-parity-gates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
12 changes: 12 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@
]
}
],
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "node \"$(git rev-parse --show-toplevel)/scripts/mosaic-parity-gate.mjs\" write",
"timeout": 10
}
]
}
],
"Stop": [
{
"hooks": [
Expand Down
12 changes: 12 additions & 0 deletions .codex/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@
}
]
}
],
"PreToolUse": [
{
"matcher": "apply_patch|Edit|Write",
"hooks": [
{
"type": "command",
"command": "node \"$(git rev-parse --show-toplevel)/scripts/mosaic-parity-gate.mjs\" write",
"timeout": 10
}
]
}
]
}
}
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ jobs:

- name: Run verify script tests
if: matrix.node-version == '24.15.0'
run: pnpm vitest run --root scripts verify-attach verify-flaky
run: pnpm vitest run --root scripts verify-attach verify-flaky mosaic-parity-gate

- name: Upload Turbo Summary
uses: actions/upload-artifact@v4.6.2
Expand Down
12 changes: 12 additions & 0 deletions packages/mosaic/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@ Before you change a file that matches a pattern below, read its guide, even for
| `src/**/*.view.tsx` | [Views](docs/views.md) |
| `src/**/__tests__/**`, `src/**/*.test.ts(x)`, `src/**/*.test-d.ts` | [Testing](docs/testing.md) |

## Ports of legacy components are migrations

For a current task that implements or reviews a Mosaic migration, or changes its migrated behavior, search `packages/react/src/components/` and `packages/ui/src/components/` for the counterpart. A headless port framed as a new feature is a [migration](docs/migration.md) too. Unrelated work does not acquire this workflow merely because the branch already contains Mosaic changes.

1. Read the counterpart, shared prop types, and relevant helper/runtime calls. Write the Phase 1 inventory to `tmp/mosaic-parity/<feature>.md` before the model. Record the task scope, exact revisions, accepted differences, and exclusions alongside the legacy path and `## Inventory` section.
2. Before you call the work done, review it, or open a PR, run the [parity audit](docs/parity-audit.md) in a read-only subagent and put its table under `## Parity audit` in the same file. Reading the doc is not running the audit.
3. Use the audit's classifications to separate parity gaps, intentional differences needing disclosure, missing verification, and optional improvements. List intentional differences and approved deferrals in the PR description. Do not require inherited improvements or refactors as parity fixes.
4. Listing a difference does not make it acceptable. Cite any conflicting rule in this file or `docs/`, and fix the gap or raise it with the user. Do not infer intentional behavior from the implementation. Preserve decisions the user already made, and leave unresolved product choices open.
5. Inspect the actual results of focused type and behavior checks. For relevant async actions, include a representative failure through the existing fake FAPI or supported browser setup. A populated note is process bookkeeping, not proof of parity.

`tmp/` is ignored by git, so the file stays out of the PR. The hooks in `.claude/settings.json` and `.codex/hooks.json` (`scripts/mosaic-parity-gate.mjs`) give conditional guidance for the feature paths in the current edit. They do not inspect branch history or notes, deny edits, or block completion. For a read-only parity review, run the explicit scoped command in [Parity audit](docs/parity-audit.md). These reminders do not run the audit or verify its evidence.

## Layers in one paragraph

A flow lives in `src/features/<feature>/`. The **model** (`*.model.ts(x)`) is the only file that reads Clerk hooks or calls Clerk resources, and answers with plain data, plain callbacks and a `status`. The **controller** (`*.controller.ts(x)`) holds local state and wraps the model's callbacks for pending, errors and closing. The **view** (`*.view.tsx`) renders plain props. No Clerk resource reaches the controller and no machine snapshot reaches the view.
Expand Down
66 changes: 47 additions & 19 deletions packages/mosaic/docs/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,19 @@ Do not write the new layers first and then ask "did I get everything?" — that
means proving a negative. Invert it: enumerate the legacy behavior first, then
make each layer account for a specific row.

Apply this workflow to the current requested Mosaic work, not every Mosaic change
already on the branch. Record the feature scope, original revisions, accepted
differences, and exclusions. When reviewing earlier feedback, use the commit the
reviewer saw, not a later implementation that may already have addressed it.

---

## Phase 1 — Inventory the legacy behavior (the spec)

Locate the legacy files (usually under `packages/ui/src/components/<Feature>/`).
Then grep them for the primitives that carry hidden logic. Every
Locate the legacy files: the prebuilt component under
`packages/ui/src/components/<Feature>/`, the `@clerk/react` component under
`packages/react/src/components/`, or both. A headless port of a `@clerk/react`
button is a migration too. Then grep them for the primitives that carry hidden logic. Every
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
hit is a row you must consciously place or drop later:

| Primitive | The behavior it usually hides |
Expand All @@ -45,13 +52,32 @@ implementation in Phase 2.

```bash
rg -n 'useEffect|handleError|card\.setError|useReverification|revalidate|<Protect|checkAuthorization|useCalloutLabel|useFetch|useInView' \
packages/ui/src/components/<Feature>/
packages/ui/src/components/<Feature>/ packages/react/src/components/<Feature>.tsx
```

Turn the hits into a **behavior inventory** — one row per: effect, guard, error
path, empty/loading state, permission gate, revalidate, reset-on-close, derived
label. This list is finite and is the contract the migration must satisfy.

Include shared prop contracts and mode-dependent options, callback ordering and
awaiting, defaults, and error delivery. Follow helpers and Clerk runtime calls
when they determine the outcome. Record inherited limitations separately from
introduced gaps. A grep hit list or comparison of component files alone is not
the complete contract. See [Parity audit](parity-audit.md) for the checks.

Write it to `tmp/mosaic-parity/<feature>.md` (ignored by git) before the model:

```md
# sign-out-button parity

Legacy: packages/react/src/components/SignOutButton.tsx

## Inventory

- `redirectUrl` defaults to `/`
- …
```

## Phase 2 — Design the split (map every row to a layer)

Assign each inventory row to exactly one layer. A row with no home is a behavior
Expand Down Expand Up @@ -97,26 +123,28 @@ row into a test there by default, and reach for a smaller test only where
highest-risk layer, so make sure its rows (revalidation, permission gates,
empty states) each have one.

## Phase 4 — Verify parity (the confidence step)
For each affected async action, exercise a representative rejection as well as
success and pending work. Verify error delivery and the final rendered/resource
state, including recovery where promised. Do not replace a Clerk mutation with a
method mock when the existing fake FAPI can drive it. Check mode-dependent prop
contracts with focused type checks and verify forwarding in the relevant modes.

Tests only cover branches you remembered to write. To catch the
ones you didn't, run an automated diff of legacy against new.
## Phase 4 — Verify parity (the confidence step)

Launch an **Explore subagent** with the prompt in `parity-audit.md`. Give it the
legacy file paths and the new model/controller/view paths. It returns a table
classifying every legacy behavior as:
Tests only cover branches you remembered to write. Have an independent reader
trace the contract and identify missing checks as well as behavioral differences.

- **Migrated** — points at a specific state / transition / context field.
- **Deliberately changed** — names the new behavior and why (e.g. infinite
scroll → "Load more" button).
- **Deferred** — a real tracked ticket, **not** a `// TODO` buried in a
controller or model. A buried TODO is invisible at review time; that is exactly
how an earlier domains-section migration shipped three regressions (see
`parity-audit.md`).
Launch a read-only subagent (Explore in Claude Code) with the prompt in `parity-audit.md`. Give it the
legacy and new revisions, file paths, shared types, and accepted decisions.
Use the classifications and evidence columns in `parity-audit.md`. Distinguish
parity gaps, intentional differences needing disclosure, missing verification,
and optional improvements. Leave unresolved intent as a decision for the user.

Every inventory row from Phase 1 must land in exactly one bucket. The table is
**ephemeral**: it drives the work and the PR discussion, then is discarded. It is
not committed.
Every inventory row from Phase 1 must have a conclusion and evidence. Put the table
under `## Parity audit` in `tmp/mosaic-parity/<feature>.md`. It drives the work
and the PR discussion, and is never committed. Read the results of its referenced
checks before claiming parity. A filled table, a tracked deferral, or a hook
reminder is not proof that the behavior works.

## Phase 5 — Ship

Expand Down
Loading
Loading