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 @@ -58,6 +58,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. The file shape and how a wrapper composes the layers are in [Features](docs/features.md).
Expand Down
35 changes: 32 additions & 3 deletions packages/mosaic/docs/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Read this when you move a legacy component (usually under `packages/ui/src/compo

Worked example: `src/features/reverification/`.

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.

## Rules

- The legacy component is the spec. A legacy file fuses rendering, data fetching and flow logic, and splitting it three ways drops behavior that was only implicit (a per-field error map, a step-up reverification, an empty-state gate, a derived callout) without failing a test or a type check.
Expand All @@ -15,7 +17,11 @@ Worked example: `src/features/reverification/`.

## Phase 1: inventory the legacy behavior

Grep the legacy files for the primitives that carry hidden logic. Every hit is a row you must place or consciously drop:
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 @@ -30,11 +36,30 @@ Grep the legacy files for the primitives that carry hidden logic. Every hit is a

```bash
rg -n 'useEffect|handleError|card\.setError|useReverification|revalidate|<Protect|checkAuthorization|useCalloutLabel|useFetch|useOrganization|\.reload\(|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 or loading state, permission gate, revalidate, reset-on-close and derived label. The table does not cover every behavior; read the files too. For shared answers (`save`, `useReverificationFlow`), use [Reuse before you write](../AGENTS.md#reuse-before-you-write).

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: map every row to a layer

- Clerk reads, mutations, permission gates, revalidate timing, first-load empty states and capability flags go in the **model** ([Models](models.md)).
Expand All @@ -56,7 +81,11 @@ Test with one `*.feature.test.tsx` that uses the feature the way a user would ([

## Phase 4: verify parity

Tests cover only the branches you remembered. Run the [Parity audit](parity-audit.md) against the legacy files and the new layers, and place every Phase 1 row as Migrated, Deliberately changed (such as infinite scroll becoming a "Load more" button) or Deferred.
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. Launch a read-only subagent with the prompt in [Parity audit](parity-audit.md). Give it the legacy and new revisions, file paths, shared types, and accepted decisions. Use the classifications and evidence columns there. 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 have a conclusion and evidence. Put the table under `## Parity audit` in `tmp/mosaic-parity/<feature>.md`, next to the Phase 1 inventory. The table guides 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