Skip to content

feat(ui): add Mosaic state machine — core library, hook, and tests - #8947

Closed
alexcarpenter wants to merge 3 commits into
mainfrom
feat/mosaic-machine-core
Closed

feat(ui): add Mosaic state machine — core library, hook, and tests#8947
alexcarpenter wants to merge 3 commits into
mainfrom
feat/mosaic-machine-core

Conversation

@alexcarpenter

@alexcarpenteralexcarpenter commented Jun 22, 2026

Copy link
Copy Markdown
Member

Summary

Introduces packages/ui/src/mosaic/machine: a tiny, dependency-free state machine runtime for modelling multi-step UI flows (confirm dialogs, sign-in wizards, async operations) without XState or any external dependency.

  • createMachine — define states, transitions, guards, and async invocations with full TypeScript inference
  • createActor — runtime that drives the machine; restarts cleanly through React StrictMode double-mount
  • setup() — typed builder with after (delayed transitions) and fromPromise (typed async invoke)
  • assign — immutable context updater helper
  • useMachine / useActor / useSelector — React bindings with stale-closure protection and setContext for syncing external context changes

Tests

  • machine.test.ts — transitions, guards, context, error states
  • useMachine.test.tsx — StrictMode behaviour, setContext, onDone
  • patterns.test.ts — common usage patterns
  • types.test-d.ts — compile-time type assertions
  • wizard-migration.test.tsx — full before/after for migrating a hand-rolled reducer flow

Stack

  1. This PR — core library
  2. feat/mosaic-machine-sections — delete/leave-org section migrations
  3. feat/mosaic-machine-signin — sign-in flow machines + adoption guide

Test plan

  • turbo test --filter @clerk/ui passes on this branch in isolation

Summary by CodeRabbit

  • New Features

    • New Mosaic state machine system for UI state, transitions, guards, async invokes, and actor runtime control
    • Added React hooks integration: useMachine, useActor, useSelector, plus optional transition logging
    • Added helper APIs for building typed machines, including setup, createMachine, assign, and promise-based invokes
    • Added actor utilities, including snapshot “teleporting” via a mock actor
  • Documentation

    • Updated and expanded guides with “first machine” examples and end-to-end migration walkthroughs
    • Added skill guidance for authoring Mosaic-style machines
  • Tests

    • Extensive new test coverage for runtime behavior, hooks, typing, and wizard/pattern parity

Introduces packages/ui/src/mosaic/machine: a tiny, dependency-free
state machine runtime for modelling multi-step flows in the Mosaic UI.
- createMachine / createActor — define states, transitions, and async
invocations (fromPromise) with full TypeScript inference
- setup() — typed builder with after (delayed transitions) and context
helpers
- assign — immutable context updater
- useMachine / useActor / useSelector — React bindings with StrictMode
safety, stale-closure protection, and setContext for syncing external
context changes
- types.test-d.ts + three test suites covering the full API and the
wizard-migration pattern
@changeset-bot

changeset-botBot commented Jun 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: de57772

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Jun 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJun 22, 2026 7:18pm
swingsetReadyReadyPreview, CommentJun 22, 2026 7:18pm

Request Review

@coderabbitai

coderabbitaiBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

An error occurred during the review process. Please try again later.

📝 Walkthrough

Walkthrough

Introduces a new dependency-free, XState v5-style state machine runtime (packages/ui/src/mosaic/machine) including a TypeScript type system, createMachine/assign/setup factories, a full createActor runtime with async invoke/after-timer/recheck support, React hooks (useMachine, useActor, useSelector), comprehensive Vitest tests, and a documentation README plus Claude SKILL guide.

Changes

Mosaic State Machine Library

Layer / File(s)Summary
Type system and contracts
packages/ui/src/mosaic/machine/types.ts
Defines all shared interfaces, types, and constants: event shapes, guard/action primitives, TransitionConfig, invoke/after event types, StateConfig, MachineConfig, StateMachine, Actor interface, Snapshot, and CreateActorOptions.
assign, createMachine, and setup factories
packages/ui/src/mosaic/machine/assign.ts, packages/ui/src/mosaic/machine/createMachine.ts, packages/ui/src/mosaic/machine/setup.ts
Implements assign action creator with isAssignAction guard, createMachine descriptor factory, and setup<TContext, TEvent>() that pre-binds generics for createMachine, assign, and typed fromPromise.
Actor runtime
packages/ui/src/mosaic/machine/createActor.ts
Full actor lifecycle: state entry/exit with action sequencing, async invoke with cancellation tokens, after timer scheduling/cancellation, guarded always transitions, public API (start, stop, send, can, setContext, recheck), snapshot caching/notification, and mockActor for inert teleported initialization.
React hooks
packages/ui/src/mosaic/machine/useMachine.ts
Implements useActor (via useSyncExternalStore), useMachine (actor ownership with lifecycle, live context injection via useLayoutEffect, onDone callback), useSelector (memoized projection with custom equality), and useMachineLogger.
TypeScript type-safety tests
packages/ui/src/mosaic/machine/types.test-d.ts
Verifies compile-time constraints via expectTypeOf and @ts-expect-error: event-union narrowing on send/can, snapshot shape, assign updater signatures, createMachine state-key constraints, setup pre-binding, and fromPromise typed output inference.
Actor runtime unit tests
packages/ui/src/mosaic/machine/__tests__/machine.test.ts
Covers machine introspection, pre-start safety, transitions/guards/actions, always, async invoke, final states, recheck(), observable contract, context initialization precedence, mockActor, pure helpers, StrictMode restart, and after-timer behavior with fake timers.
Real-world UI pattern tests
packages/ui/src/mosaic/machine/__tests__/patterns.test.ts
Verifies eight Clerk UI coordination patterns: multi-entry loading, resend cooldown, error-clear-on-input, conditional initial state, post-invoke routing, typed fromPromise output, on-mount async invoke, and reactive value auto-switch.
React hook and wizard migration tests
packages/ui/src/mosaic/machine/__tests__/useMachine.test.tsx, packages/ui/src/mosaic/machine/__tests__/wizard-migration.test.tsx
Tests useMachine/useActor/useSelector integration (context injection, teleport, re-render scoping, onDone, live context). Builds and tests a createWizardMachine covering derived initial, NEXT/PREV/GOTO navigation, deriveStepper introspection, recheck() re-seating, boundary-signal proxy, and mockActor gated-step teleport.
Documentation and changeset
packages/ui/src/mosaic/machine/README.md, .claude/skills/mosaic-machine/SKILL.md, .changeset/mosaic-state-machine.md
Full machine README with first-machine walkthrough, building blocks, API table, no-op/recheck semantics, and ConfigureSSO Wizard migration example. Claude SKILL guide with do/don't conventions, setup typing guide, and React consumption patterns. Changeset version marker.

Sequence Diagram(s)

sequenceDiagram
participant UI
participant Hook
participant Runtime
participant Machine
UI->>Hook: mount with machine and options
Hook->>Runtime: createActor
Runtime->>Machine: resolve initial state and context
Hook->>Runtime: start
UI->>Hook: send event
Hook->>Runtime: send
Runtime->>Machine: pick and take transition
Runtime-->>Hook: publish snapshot
Hook-->>UI: render with snapshot
UI->>Hook: unmount
Hook->>Runtime: stop
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Suggested labels

core-3

Suggested reviewers

  • brkalow
  • Ephem

Poem

🐇 Hop, hop! A machine is born today,
States and events to light the way.
assign patches context neat and clean,
always guards flow through the in-between.
useMachine mounts, then stop() on leave —
A rabbit-built runtime you can trust and believe! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 40.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat(ui): add Mosaic state machine — core library, hook, and tests' accurately describes the main change: introduction of a new state machine library with core APIs, React hooks, and comprehensive test coverage for managing multi-step UI flows.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new

pkg-pr-newBot commented Jun 22, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8947

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8947

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8947

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8947

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@8947

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8947

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8947

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8947

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8947

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8947

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8947

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8947

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8947

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8947

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8947

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8947

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8947

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8947

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8947

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8947

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8947

commit: de57772

@github-actions

github-actionsBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-22T19:19:47.984Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on de57772.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
packages/ui/src/mosaic/machine/setup.ts (1)

77-82: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Prefer a typed wrapper over double-casting in fromPromise (Line 81).

fn as unknown as ... hides contract drift. A tiny adapter preserves the same runtime behavior and keeps type checking honest.

💡 Proposed change
- src: fn as unknown as InvokeConfig<TContext, TEvent, TOutput, TStates>['src'],+ src: (context, _event) => fn(context),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/mosaic/machine/setup.ts` around lines 77 - 82, The
double-casting pattern in the src property assignment within fromPromise hides
type contract drift and should be replaced with a typed wrapper function. Create
a wrapper function that takes the context and event parameters (matching
InvokeConfig src signature) but only passes the context parameter to the
original fn, discarding the event parameter. This wrapper preserves the exact
same runtime behavior while maintaining proper type safety without hidden casts.
Assign this wrapper function to src instead of using the double-cast pattern.
packages/ui/src/mosaic/machine/__tests__/machine.test.ts (1)

220-314: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add two invoke regression tests: sync-throw and guard-blocked completion no-op.

The invoke suite is strong, but it should also pin:

  1. invoke.src synchronous throw routes via onError (no actor crash), and
  2. onDone/onError transition blocked by target entry guard is a true no-op (no notify/ref churn).

As per coding guidelines, “Unit tests are required for all new functionality” and “Verify proper error handling and edge cases.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/mosaic/machine/__tests__/machine.test.ts` around lines 220 -
314, Add two new test cases to the existing describe block 'createActor — invoke
(async)'. First, add a test that verifies when invoke.src throws synchronously,
the error is properly routed to onError and the actor does not crash. Second,
add a test that verifies when an onDone or onError transition has a target with
an entry guard that blocks the transition, it results in a true no-op without
any notification or reference management churn. Both tests should follow the
same pattern as the existing tests in the suite, using createMachine,
createActor, and tick utilities to verify the expected behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/ui/src/mosaic/machine/createActor.ts`:
- Around line 145-154: The error handler for invoke.onError is calling commit()
unconditionally after takeTransition(), but takeTransition() can return false
when a transition is blocked by an entry guard. Modify the code to check the
return value of takeTransition() and only call commit() if it returns true. This
same pattern should also be applied to the onDone handler above this code block
to ensure no unnecessary commits occur on blocked transitions.
- Around line 139-156: The current Promise.resolve(invoke.src(context, event as
never)) eagerly evaluates the invoke.src call, which means synchronous errors
thrown from invoke.src escape the promise chain instead of being caught and
routed to the onError handler. To fix this, wrap the invoke.src call in a
deferred microtask by changing Promise.resolve(invoke.src(...)) to
Promise.resolve().then(() => invoke.src(...)) so that both synchronous and
asynchronous errors are consistently caught by the error rejection handler that
calls the onError transition.
In `@packages/ui/src/mosaic/machine/setup.ts`:
- Around line 41-85: The exported setup function at line 41 lacks an explicit
return type annotation, which violates the repository's TypeScript guidelines
for public APIs. Add an explicit return type to the setup function that
describes the object structure it returns, which includes the createMachine,
assign, and fromPromise methods with their respective signatures. The return
type should be defined immediately after the function's closing parenthesis and
before the opening brace of the function body.
---
Nitpick comments:
In `@packages/ui/src/mosaic/machine/__tests__/machine.test.ts`:
- Around line 220-314: Add two new test cases to the existing describe block
'createActor — invoke (async)'. First, add a test that verifies when invoke.src
throws synchronously, the error is properly routed to onError and the actor does
not crash. Second, add a test that verifies when an onDone or onError transition
has a target with an entry guard that blocks the transition, it results in a
true no-op without any notification or reference management churn. Both tests
should follow the same pattern as the existing tests in the suite, using
createMachine, createActor, and tick utilities to verify the expected behavior.
In `@packages/ui/src/mosaic/machine/setup.ts`:
- Around line 77-82: The double-casting pattern in the src property assignment
within fromPromise hides type contract drift and should be replaced with a typed
wrapper function. Create a wrapper function that takes the context and event
parameters (matching InvokeConfig src signature) but only passes the context
parameter to the original fn, discarding the event parameter. This wrapper
preserves the exact same runtime behavior while maintaining proper type safety
without hidden casts. Assign this wrapper function to src instead of using the
double-cast pattern.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: d5481399-00a3-4632-9c85-99189d93cb08

📥 Commits

Reviewing files that changed from the base of the PR and between 7e3174a and dce39c1.

📒 Files selected for processing (14)
  • .changeset/mosaic-state-machine.md
  • .claude/skills/mosaic-machine/SKILL.md
  • packages/ui/src/mosaic/machine/README.md
  • packages/ui/src/mosaic/machine/__tests__/machine.test.ts
  • packages/ui/src/mosaic/machine/__tests__/patterns.test.ts
  • packages/ui/src/mosaic/machine/__tests__/useMachine.test.tsx
  • packages/ui/src/mosaic/machine/__tests__/wizard-migration.test.tsx
  • packages/ui/src/mosaic/machine/assign.ts
  • packages/ui/src/mosaic/machine/createActor.ts
  • packages/ui/src/mosaic/machine/createMachine.ts
  • packages/ui/src/mosaic/machine/setup.ts
  • packages/ui/src/mosaic/machine/types.test-d.ts
  • packages/ui/src/mosaic/machine/types.ts
  • packages/ui/src/mosaic/machine/useMachine.ts

Comment threadpackages/ui/src/mosaic/machine/createActor.ts Outdated
Comment threadpackages/ui/src/mosaic/machine/createActor.ts Outdated
Comment threadpackages/ui/src/mosaic/machine/setup.ts Outdated
- createActor: wrap invoke src in Promise constructor so a synchronous
throw routes to onError rather than escaping as an unhandled exception
(Promise.resolve(fn()) evaluates fn() before the chain begins)
- createActor: guard commit() on takeTransition return value for
onDone/onError handlers, matching the existing after-timer pattern
- setup: add SetupTools interface and explicit return type annotation
Promise.resolve(src()) returns the same native Promise with zero extra
microtask hops; the previous new Promise(r => r(thenable)) added two
hops via the thenable resolution procedure, causing the cooldown-timer
tests to read state before onDone had fired.
Add delete-organization-machine.ts test fixture to the core branch so
machine.test.ts and useMachine.test.tsx compile without a dep on the
sections layer.
@alexcarpenter

Copy link
Copy Markdown
MemberAuthor

closing in favor of #8919

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@alexcarpenter@wobsoriano
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
feat(ui): add Mosaic state machine — core library, hook, and tests by alexcarpenter · Pull Request #8947 · clerk/javascript · GitHub
Skip to content

feat(ui): add Mosaic state machine — core library, hook, and tests - #8947

Closed
alexcarpenter wants to merge 3 commits into
mainfrom
feat/mosaic-machine-core
Closed

feat(ui): add Mosaic state machine — core library, hook, and tests#8947
alexcarpenter wants to merge 3 commits into
mainfrom
feat/mosaic-machine-core

Conversation

@alexcarpenter

@alexcarpenteralexcarpenter commented Jun 22, 2026

Copy link
Copy Markdown
Member

Summary

Introduces packages/ui/src/mosaic/machine: a tiny, dependency-free state machine runtime for modelling multi-step UI flows (confirm dialogs, sign-in wizards, async operations) without XState or any external dependency.

  • createMachine — define states, transitions, guards, and async invocations with full TypeScript inference
  • createActor — runtime that drives the machine; restarts cleanly through React StrictMode double-mount
  • setup() — typed builder with after (delayed transitions) and fromPromise (typed async invoke)
  • assign — immutable context updater helper
  • useMachine / useActor / useSelector — React bindings with stale-closure protection and setContext for syncing external context changes

Tests

  • machine.test.ts — transitions, guards, context, error states
  • useMachine.test.tsx — StrictMode behaviour, setContext, onDone
  • patterns.test.ts — common usage patterns
  • types.test-d.ts — compile-time type assertions
  • wizard-migration.test.tsx — full before/after for migrating a hand-rolled reducer flow

Stack

  1. This PR — core library
  2. feat/mosaic-machine-sections — delete/leave-org section migrations
  3. feat/mosaic-machine-signin — sign-in flow machines + adoption guide

Test plan

  • turbo test --filter @clerk/ui passes on this branch in isolation

Summary by CodeRabbit

  • New Features

    • New Mosaic state machine system for UI state, transitions, guards, async invokes, and actor runtime control
    • Added React hooks integration: useMachine, useActor, useSelector, plus optional transition logging
    • Added helper APIs for building typed machines, including setup, createMachine, assign, and promise-based invokes
    • Added actor utilities, including snapshot “teleporting” via a mock actor
  • Documentation

    • Updated and expanded guides with “first machine” examples and end-to-end migration walkthroughs
    • Added skill guidance for authoring Mosaic-style machines
  • Tests

    • Extensive new test coverage for runtime behavior, hooks, typing, and wizard/pattern parity

Introduces packages/ui/src/mosaic/machine: a tiny, dependency-free
state machine runtime for modelling multi-step flows in the Mosaic UI.
- createMachine / createActor — define states, transitions, and async
invocations (fromPromise) with full TypeScript inference
- setup() — typed builder with after (delayed transitions) and context
helpers
- assign — immutable context updater
- useMachine / useActor / useSelector — React bindings with StrictMode
safety, stale-closure protection, and setContext for syncing external
context changes
- types.test-d.ts + three test suites covering the full API and the
wizard-migration pattern
@changeset-bot

changeset-botBot commented Jun 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: de57772

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Jun 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJun 22, 2026 7:18pm
swingsetReadyReadyPreview, CommentJun 22, 2026 7:18pm

Request Review

@coderabbitai

coderabbitaiBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

An error occurred during the review process. Please try again later.

📝 Walkthrough

Walkthrough

Introduces a new dependency-free, XState v5-style state machine runtime (packages/ui/src/mosaic/machine) including a TypeScript type system, createMachine/assign/setup factories, a full createActor runtime with async invoke/after-timer/recheck support, React hooks (useMachine, useActor, useSelector), comprehensive Vitest tests, and a documentation README plus Claude SKILL guide.

Changes

Mosaic State Machine Library

Layer / File(s)Summary
Type system and contracts
packages/ui/src/mosaic/machine/types.ts
Defines all shared interfaces, types, and constants: event shapes, guard/action primitives, TransitionConfig, invoke/after event types, StateConfig, MachineConfig, StateMachine, Actor interface, Snapshot, and CreateActorOptions.
assign, createMachine, and setup factories
packages/ui/src/mosaic/machine/assign.ts, packages/ui/src/mosaic/machine/createMachine.ts, packages/ui/src/mosaic/machine/setup.ts
Implements assign action creator with isAssignAction guard, createMachine descriptor factory, and setup<TContext, TEvent>() that pre-binds generics for createMachine, assign, and typed fromPromise.
Actor runtime
packages/ui/src/mosaic/machine/createActor.ts
Full actor lifecycle: state entry/exit with action sequencing, async invoke with cancellation tokens, after timer scheduling/cancellation, guarded always transitions, public API (start, stop, send, can, setContext, recheck), snapshot caching/notification, and mockActor for inert teleported initialization.
React hooks
packages/ui/src/mosaic/machine/useMachine.ts
Implements useActor (via useSyncExternalStore), useMachine (actor ownership with lifecycle, live context injection via useLayoutEffect, onDone callback), useSelector (memoized projection with custom equality), and useMachineLogger.
TypeScript type-safety tests
packages/ui/src/mosaic/machine/types.test-d.ts
Verifies compile-time constraints via expectTypeOf and @ts-expect-error: event-union narrowing on send/can, snapshot shape, assign updater signatures, createMachine state-key constraints, setup pre-binding, and fromPromise typed output inference.
Actor runtime unit tests
packages/ui/src/mosaic/machine/__tests__/machine.test.ts
Covers machine introspection, pre-start safety, transitions/guards/actions, always, async invoke, final states, recheck(), observable contract, context initialization precedence, mockActor, pure helpers, StrictMode restart, and after-timer behavior with fake timers.
Real-world UI pattern tests
packages/ui/src/mosaic/machine/__tests__/patterns.test.ts
Verifies eight Clerk UI coordination patterns: multi-entry loading, resend cooldown, error-clear-on-input, conditional initial state, post-invoke routing, typed fromPromise output, on-mount async invoke, and reactive value auto-switch.
React hook and wizard migration tests
packages/ui/src/mosaic/machine/__tests__/useMachine.test.tsx, packages/ui/src/mosaic/machine/__tests__/wizard-migration.test.tsx
Tests useMachine/useActor/useSelector integration (context injection, teleport, re-render scoping, onDone, live context). Builds and tests a createWizardMachine covering derived initial, NEXT/PREV/GOTO navigation, deriveStepper introspection, recheck() re-seating, boundary-signal proxy, and mockActor gated-step teleport.
Documentation and changeset
packages/ui/src/mosaic/machine/README.md, .claude/skills/mosaic-machine/SKILL.md, .changeset/mosaic-state-machine.md
Full machine README with first-machine walkthrough, building blocks, API table, no-op/recheck semantics, and ConfigureSSO Wizard migration example. Claude SKILL guide with do/don't conventions, setup typing guide, and React consumption patterns. Changeset version marker.

Sequence Diagram(s)

sequenceDiagram
participant UI
participant Hook
participant Runtime
participant Machine
UI->>Hook: mount with machine and options
Hook->>Runtime: createActor
Runtime->>Machine: resolve initial state and context
Hook->>Runtime: start
UI->>Hook: send event
Hook->>Runtime: send
Runtime->>Machine: pick and take transition
Runtime-->>Hook: publish snapshot
Hook-->>UI: render with snapshot
UI->>Hook: unmount
Hook->>Runtime: stop
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Suggested labels

core-3

Suggested reviewers

  • brkalow
  • Ephem

Poem

🐇 Hop, hop! A machine is born today,
States and events to light the way.
assign patches context neat and clean,
always guards flow through the in-between.
useMachine mounts, then stop() on leave —
A rabbit-built runtime you can trust and believe! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 40.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat(ui): add Mosaic state machine — core library, hook, and tests' accurately describes the main change: introduction of a new state machine library with core APIs, React hooks, and comprehensive test coverage for managing multi-step UI flows.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new

pkg-pr-newBot commented Jun 22, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8947

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8947

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8947

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8947

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@8947

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8947

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8947

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8947

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8947

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8947

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8947

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8947

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8947

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8947

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8947

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8947

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8947

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8947

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8947

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8947

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8947

commit: de57772

@github-actions

github-actionsBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-22T19:19:47.984Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on de57772.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
packages/ui/src/mosaic/machine/setup.ts (1)

77-82: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Prefer a typed wrapper over double-casting in fromPromise (Line 81).

fn as unknown as ... hides contract drift. A tiny adapter preserves the same runtime behavior and keeps type checking honest.

💡 Proposed change
- src: fn as unknown as InvokeConfig<TContext, TEvent, TOutput, TStates>['src'],+ src: (context, _event) => fn(context),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/mosaic/machine/setup.ts` around lines 77 - 82, The
double-casting pattern in the src property assignment within fromPromise hides
type contract drift and should be replaced with a typed wrapper function. Create
a wrapper function that takes the context and event parameters (matching
InvokeConfig src signature) but only passes the context parameter to the
original fn, discarding the event parameter. This wrapper preserves the exact
same runtime behavior while maintaining proper type safety without hidden casts.
Assign this wrapper function to src instead of using the double-cast pattern.
packages/ui/src/mosaic/machine/__tests__/machine.test.ts (1)

220-314: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add two invoke regression tests: sync-throw and guard-blocked completion no-op.

The invoke suite is strong, but it should also pin:

  1. invoke.src synchronous throw routes via onError (no actor crash), and
  2. onDone/onError transition blocked by target entry guard is a true no-op (no notify/ref churn).

As per coding guidelines, “Unit tests are required for all new functionality” and “Verify proper error handling and edge cases.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/mosaic/machine/__tests__/machine.test.ts` around lines 220 -
314, Add two new test cases to the existing describe block 'createActor — invoke
(async)'. First, add a test that verifies when invoke.src throws synchronously,
the error is properly routed to onError and the actor does not crash. Second,
add a test that verifies when an onDone or onError transition has a target with
an entry guard that blocks the transition, it results in a true no-op without
any notification or reference management churn. Both tests should follow the
same pattern as the existing tests in the suite, using createMachine,
createActor, and tick utilities to verify the expected behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/ui/src/mosaic/machine/createActor.ts`:
- Around line 145-154: The error handler for invoke.onError is calling commit()
unconditionally after takeTransition(), but takeTransition() can return false
when a transition is blocked by an entry guard. Modify the code to check the
return value of takeTransition() and only call commit() if it returns true. This
same pattern should also be applied to the onDone handler above this code block
to ensure no unnecessary commits occur on blocked transitions.
- Around line 139-156: The current Promise.resolve(invoke.src(context, event as
never)) eagerly evaluates the invoke.src call, which means synchronous errors
thrown from invoke.src escape the promise chain instead of being caught and
routed to the onError handler. To fix this, wrap the invoke.src call in a
deferred microtask by changing Promise.resolve(invoke.src(...)) to
Promise.resolve().then(() => invoke.src(...)) so that both synchronous and
asynchronous errors are consistently caught by the error rejection handler that
calls the onError transition.
In `@packages/ui/src/mosaic/machine/setup.ts`:
- Around line 41-85: The exported setup function at line 41 lacks an explicit
return type annotation, which violates the repository's TypeScript guidelines
for public APIs. Add an explicit return type to the setup function that
describes the object structure it returns, which includes the createMachine,
assign, and fromPromise methods with their respective signatures. The return
type should be defined immediately after the function's closing parenthesis and
before the opening brace of the function body.
---
Nitpick comments:
In `@packages/ui/src/mosaic/machine/__tests__/machine.test.ts`:
- Around line 220-314: Add two new test cases to the existing describe block
'createActor — invoke (async)'. First, add a test that verifies when invoke.src
throws synchronously, the error is properly routed to onError and the actor does
not crash. Second, add a test that verifies when an onDone or onError transition
has a target with an entry guard that blocks the transition, it results in a
true no-op without any notification or reference management churn. Both tests
should follow the same pattern as the existing tests in the suite, using
createMachine, createActor, and tick utilities to verify the expected behavior.
In `@packages/ui/src/mosaic/machine/setup.ts`:
- Around line 77-82: The double-casting pattern in the src property assignment
within fromPromise hides type contract drift and should be replaced with a typed
wrapper function. Create a wrapper function that takes the context and event
parameters (matching InvokeConfig src signature) but only passes the context
parameter to the original fn, discarding the event parameter. This wrapper
preserves the exact same runtime behavior while maintaining proper type safety
without hidden casts. Assign this wrapper function to src instead of using the
double-cast pattern.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: d5481399-00a3-4632-9c85-99189d93cb08

📥 Commits

Reviewing files that changed from the base of the PR and between 7e3174a and dce39c1.

📒 Files selected for processing (14)
  • .changeset/mosaic-state-machine.md
  • .claude/skills/mosaic-machine/SKILL.md
  • packages/ui/src/mosaic/machine/README.md
  • packages/ui/src/mosaic/machine/__tests__/machine.test.ts
  • packages/ui/src/mosaic/machine/__tests__/patterns.test.ts
  • packages/ui/src/mosaic/machine/__tests__/useMachine.test.tsx
  • packages/ui/src/mosaic/machine/__tests__/wizard-migration.test.tsx
  • packages/ui/src/mosaic/machine/assign.ts
  • packages/ui/src/mosaic/machine/createActor.ts
  • packages/ui/src/mosaic/machine/createMachine.ts
  • packages/ui/src/mosaic/machine/setup.ts
  • packages/ui/src/mosaic/machine/types.test-d.ts
  • packages/ui/src/mosaic/machine/types.ts
  • packages/ui/src/mosaic/machine/useMachine.ts

Comment threadpackages/ui/src/mosaic/machine/createActor.ts Outdated
Comment threadpackages/ui/src/mosaic/machine/createActor.ts Outdated
Comment threadpackages/ui/src/mosaic/machine/setup.ts Outdated
- createActor: wrap invoke src in Promise constructor so a synchronous
throw routes to onError rather than escaping as an unhandled exception
(Promise.resolve(fn()) evaluates fn() before the chain begins)
- createActor: guard commit() on takeTransition return value for
onDone/onError handlers, matching the existing after-timer pattern
- setup: add SetupTools interface and explicit return type annotation
Promise.resolve(src()) returns the same native Promise with zero extra
microtask hops; the previous new Promise(r => r(thenable)) added two
hops via the thenable resolution procedure, causing the cooldown-timer
tests to read state before onDone had fired.
Add delete-organization-machine.ts test fixture to the core branch so
machine.test.ts and useMachine.test.tsx compile without a dep on the
sections layer.
@alexcarpenter

Copy link
Copy Markdown
MemberAuthor

closing in favor of #8919

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@alexcarpenter@wobsoriano
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(ui): add Mosaic state machine — core library, hook, and tests by alexcarpenter · Pull Request #8947 · clerk/javascript · GitHub
Skip to content

feat(ui): add Mosaic state machine — core library, hook, and tests - #8947

Closed
alexcarpenter wants to merge 3 commits into
mainfrom
feat/mosaic-machine-core
Closed

feat(ui): add Mosaic state machine — core library, hook, and tests#8947
alexcarpenter wants to merge 3 commits into
mainfrom
feat/mosaic-machine-core

Conversation

@alexcarpenter

@alexcarpenteralexcarpenter commented Jun 22, 2026

Copy link
Copy Markdown
Member

Summary

Introduces packages/ui/src/mosaic/machine: a tiny, dependency-free state machine runtime for modelling multi-step UI flows (confirm dialogs, sign-in wizards, async operations) without XState or any external dependency.

  • createMachine — define states, transitions, guards, and async invocations with full TypeScript inference
  • createActor — runtime that drives the machine; restarts cleanly through React StrictMode double-mount
  • setup() — typed builder with after (delayed transitions) and fromPromise (typed async invoke)
  • assign — immutable context updater helper
  • useMachine / useActor / useSelector — React bindings with stale-closure protection and setContext for syncing external context changes

Tests

  • machine.test.ts — transitions, guards, context, error states
  • useMachine.test.tsx — StrictMode behaviour, setContext, onDone
  • patterns.test.ts — common usage patterns
  • types.test-d.ts — compile-time type assertions
  • wizard-migration.test.tsx — full before/after for migrating a hand-rolled reducer flow

Stack

  1. This PR — core library
  2. feat/mosaic-machine-sections — delete/leave-org section migrations
  3. feat/mosaic-machine-signin — sign-in flow machines + adoption guide

Test plan

  • turbo test --filter @clerk/ui passes on this branch in isolation

Summary by CodeRabbit

  • New Features

    • New Mosaic state machine system for UI state, transitions, guards, async invokes, and actor runtime control
    • Added React hooks integration: useMachine, useActor, useSelector, plus optional transition logging
    • Added helper APIs for building typed machines, including setup, createMachine, assign, and promise-based invokes
    • Added actor utilities, including snapshot “teleporting” via a mock actor
  • Documentation

    • Updated and expanded guides with “first machine” examples and end-to-end migration walkthroughs
    • Added skill guidance for authoring Mosaic-style machines
  • Tests

    • Extensive new test coverage for runtime behavior, hooks, typing, and wizard/pattern parity

Introduces packages/ui/src/mosaic/machine: a tiny, dependency-free
state machine runtime for modelling multi-step flows in the Mosaic UI.
- createMachine / createActor — define states, transitions, and async
invocations (fromPromise) with full TypeScript inference
- setup() — typed builder with after (delayed transitions) and context
helpers
- assign — immutable context updater
- useMachine / useActor / useSelector — React bindings with StrictMode
safety, stale-closure protection, and setContext for syncing external
context changes
- types.test-d.ts + three test suites covering the full API and the
wizard-migration pattern
@changeset-bot

changeset-botBot commented Jun 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: de57772

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Jun 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJun 22, 2026 7:18pm
swingsetReadyReadyPreview, CommentJun 22, 2026 7:18pm

Request Review

@coderabbitai

coderabbitaiBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

An error occurred during the review process. Please try again later.

📝 Walkthrough

Walkthrough

Introduces a new dependency-free, XState v5-style state machine runtime (packages/ui/src/mosaic/machine) including a TypeScript type system, createMachine/assign/setup factories, a full createActor runtime with async invoke/after-timer/recheck support, React hooks (useMachine, useActor, useSelector), comprehensive Vitest tests, and a documentation README plus Claude SKILL guide.

Changes

Mosaic State Machine Library

Layer / File(s)Summary
Type system and contracts
packages/ui/src/mosaic/machine/types.ts
Defines all shared interfaces, types, and constants: event shapes, guard/action primitives, TransitionConfig, invoke/after event types, StateConfig, MachineConfig, StateMachine, Actor interface, Snapshot, and CreateActorOptions.
assign, createMachine, and setup factories
packages/ui/src/mosaic/machine/assign.ts, packages/ui/src/mosaic/machine/createMachine.ts, packages/ui/src/mosaic/machine/setup.ts
Implements assign action creator with isAssignAction guard, createMachine descriptor factory, and setup<TContext, TEvent>() that pre-binds generics for createMachine, assign, and typed fromPromise.
Actor runtime
packages/ui/src/mosaic/machine/createActor.ts
Full actor lifecycle: state entry/exit with action sequencing, async invoke with cancellation tokens, after timer scheduling/cancellation, guarded always transitions, public API (start, stop, send, can, setContext, recheck), snapshot caching/notification, and mockActor for inert teleported initialization.
React hooks
packages/ui/src/mosaic/machine/useMachine.ts
Implements useActor (via useSyncExternalStore), useMachine (actor ownership with lifecycle, live context injection via useLayoutEffect, onDone callback), useSelector (memoized projection with custom equality), and useMachineLogger.
TypeScript type-safety tests
packages/ui/src/mosaic/machine/types.test-d.ts
Verifies compile-time constraints via expectTypeOf and @ts-expect-error: event-union narrowing on send/can, snapshot shape, assign updater signatures, createMachine state-key constraints, setup pre-binding, and fromPromise typed output inference.
Actor runtime unit tests
packages/ui/src/mosaic/machine/__tests__/machine.test.ts
Covers machine introspection, pre-start safety, transitions/guards/actions, always, async invoke, final states, recheck(), observable contract, context initialization precedence, mockActor, pure helpers, StrictMode restart, and after-timer behavior with fake timers.
Real-world UI pattern tests
packages/ui/src/mosaic/machine/__tests__/patterns.test.ts
Verifies eight Clerk UI coordination patterns: multi-entry loading, resend cooldown, error-clear-on-input, conditional initial state, post-invoke routing, typed fromPromise output, on-mount async invoke, and reactive value auto-switch.
React hook and wizard migration tests
packages/ui/src/mosaic/machine/__tests__/useMachine.test.tsx, packages/ui/src/mosaic/machine/__tests__/wizard-migration.test.tsx
Tests useMachine/useActor/useSelector integration (context injection, teleport, re-render scoping, onDone, live context). Builds and tests a createWizardMachine covering derived initial, NEXT/PREV/GOTO navigation, deriveStepper introspection, recheck() re-seating, boundary-signal proxy, and mockActor gated-step teleport.
Documentation and changeset
packages/ui/src/mosaic/machine/README.md, .claude/skills/mosaic-machine/SKILL.md, .changeset/mosaic-state-machine.md
Full machine README with first-machine walkthrough, building blocks, API table, no-op/recheck semantics, and ConfigureSSO Wizard migration example. Claude SKILL guide with do/don't conventions, setup typing guide, and React consumption patterns. Changeset version marker.

Sequence Diagram(s)

sequenceDiagram
participant UI
participant Hook
participant Runtime
participant Machine
UI->>Hook: mount with machine and options
Hook->>Runtime: createActor
Runtime->>Machine: resolve initial state and context
Hook->>Runtime: start
UI->>Hook: send event
Hook->>Runtime: send
Runtime->>Machine: pick and take transition
Runtime-->>Hook: publish snapshot
Hook-->>UI: render with snapshot
UI->>Hook: unmount
Hook->>Runtime: stop
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Suggested labels

core-3

Suggested reviewers

  • brkalow
  • Ephem

Poem

🐇 Hop, hop! A machine is born today,
States and events to light the way.
assign patches context neat and clean,
always guards flow through the in-between.
useMachine mounts, then stop() on leave —
A rabbit-built runtime you can trust and believe! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 40.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat(ui): add Mosaic state machine — core library, hook, and tests' accurately describes the main change: introduction of a new state machine library with core APIs, React hooks, and comprehensive test coverage for managing multi-step UI flows.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new

pkg-pr-newBot commented Jun 22, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8947

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8947

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8947

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8947

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@8947

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8947

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8947

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8947

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8947

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8947

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8947

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8947

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8947

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8947

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8947

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8947

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8947

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8947

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8947

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8947

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8947

commit: de57772

@github-actions

github-actionsBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-22T19:19:47.984Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on de57772.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
packages/ui/src/mosaic/machine/setup.ts (1)

77-82: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Prefer a typed wrapper over double-casting in fromPromise (Line 81).

fn as unknown as ... hides contract drift. A tiny adapter preserves the same runtime behavior and keeps type checking honest.

💡 Proposed change
- src: fn as unknown as InvokeConfig<TContext, TEvent, TOutput, TStates>['src'],+ src: (context, _event) => fn(context),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/mosaic/machine/setup.ts` around lines 77 - 82, The
double-casting pattern in the src property assignment within fromPromise hides
type contract drift and should be replaced with a typed wrapper function. Create
a wrapper function that takes the context and event parameters (matching
InvokeConfig src signature) but only passes the context parameter to the
original fn, discarding the event parameter. This wrapper preserves the exact
same runtime behavior while maintaining proper type safety without hidden casts.
Assign this wrapper function to src instead of using the double-cast pattern.
packages/ui/src/mosaic/machine/__tests__/machine.test.ts (1)

220-314: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add two invoke regression tests: sync-throw and guard-blocked completion no-op.

The invoke suite is strong, but it should also pin:

  1. invoke.src synchronous throw routes via onError (no actor crash), and
  2. onDone/onError transition blocked by target entry guard is a true no-op (no notify/ref churn).

As per coding guidelines, “Unit tests are required for all new functionality” and “Verify proper error handling and edge cases.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/mosaic/machine/__tests__/machine.test.ts` around lines 220 -
314, Add two new test cases to the existing describe block 'createActor — invoke
(async)'. First, add a test that verifies when invoke.src throws synchronously,
the error is properly routed to onError and the actor does not crash. Second,
add a test that verifies when an onDone or onError transition has a target with
an entry guard that blocks the transition, it results in a true no-op without
any notification or reference management churn. Both tests should follow the
same pattern as the existing tests in the suite, using createMachine,
createActor, and tick utilities to verify the expected behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/ui/src/mosaic/machine/createActor.ts`:
- Around line 145-154: The error handler for invoke.onError is calling commit()
unconditionally after takeTransition(), but takeTransition() can return false
when a transition is blocked by an entry guard. Modify the code to check the
return value of takeTransition() and only call commit() if it returns true. This
same pattern should also be applied to the onDone handler above this code block
to ensure no unnecessary commits occur on blocked transitions.
- Around line 139-156: The current Promise.resolve(invoke.src(context, event as
never)) eagerly evaluates the invoke.src call, which means synchronous errors
thrown from invoke.src escape the promise chain instead of being caught and
routed to the onError handler. To fix this, wrap the invoke.src call in a
deferred microtask by changing Promise.resolve(invoke.src(...)) to
Promise.resolve().then(() => invoke.src(...)) so that both synchronous and
asynchronous errors are consistently caught by the error rejection handler that
calls the onError transition.
In `@packages/ui/src/mosaic/machine/setup.ts`:
- Around line 41-85: The exported setup function at line 41 lacks an explicit
return type annotation, which violates the repository's TypeScript guidelines
for public APIs. Add an explicit return type to the setup function that
describes the object structure it returns, which includes the createMachine,
assign, and fromPromise methods with their respective signatures. The return
type should be defined immediately after the function's closing parenthesis and
before the opening brace of the function body.
---
Nitpick comments:
In `@packages/ui/src/mosaic/machine/__tests__/machine.test.ts`:
- Around line 220-314: Add two new test cases to the existing describe block
'createActor — invoke (async)'. First, add a test that verifies when invoke.src
throws synchronously, the error is properly routed to onError and the actor does
not crash. Second, add a test that verifies when an onDone or onError transition
has a target with an entry guard that blocks the transition, it results in a
true no-op without any notification or reference management churn. Both tests
should follow the same pattern as the existing tests in the suite, using
createMachine, createActor, and tick utilities to verify the expected behavior.
In `@packages/ui/src/mosaic/machine/setup.ts`:
- Around line 77-82: The double-casting pattern in the src property assignment
within fromPromise hides type contract drift and should be replaced with a typed
wrapper function. Create a wrapper function that takes the context and event
parameters (matching InvokeConfig src signature) but only passes the context
parameter to the original fn, discarding the event parameter. This wrapper
preserves the exact same runtime behavior while maintaining proper type safety
without hidden casts. Assign this wrapper function to src instead of using the
double-cast pattern.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: d5481399-00a3-4632-9c85-99189d93cb08

📥 Commits

Reviewing files that changed from the base of the PR and between 7e3174a and dce39c1.

📒 Files selected for processing (14)
  • .changeset/mosaic-state-machine.md
  • .claude/skills/mosaic-machine/SKILL.md
  • packages/ui/src/mosaic/machine/README.md
  • packages/ui/src/mosaic/machine/__tests__/machine.test.ts
  • packages/ui/src/mosaic/machine/__tests__/patterns.test.ts
  • packages/ui/src/mosaic/machine/__tests__/useMachine.test.tsx
  • packages/ui/src/mosaic/machine/__tests__/wizard-migration.test.tsx
  • packages/ui/src/mosaic/machine/assign.ts
  • packages/ui/src/mosaic/machine/createActor.ts
  • packages/ui/src/mosaic/machine/createMachine.ts
  • packages/ui/src/mosaic/machine/setup.ts
  • packages/ui/src/mosaic/machine/types.test-d.ts
  • packages/ui/src/mosaic/machine/types.ts
  • packages/ui/src/mosaic/machine/useMachine.ts

Comment threadpackages/ui/src/mosaic/machine/createActor.ts Outdated
Comment threadpackages/ui/src/mosaic/machine/createActor.ts Outdated
Comment threadpackages/ui/src/mosaic/machine/setup.ts Outdated
- createActor: wrap invoke src in Promise constructor so a synchronous
throw routes to onError rather than escaping as an unhandled exception
(Promise.resolve(fn()) evaluates fn() before the chain begins)
- createActor: guard commit() on takeTransition return value for
onDone/onError handlers, matching the existing after-timer pattern
- setup: add SetupTools interface and explicit return type annotation
Promise.resolve(src()) returns the same native Promise with zero extra
microtask hops; the previous new Promise(r => r(thenable)) added two
hops via the thenable resolution procedure, causing the cooldown-timer
tests to read state before onDone had fired.
Add delete-organization-machine.ts test fixture to the core branch so
machine.test.ts and useMachine.test.tsx compile without a dep on the
sections layer.
@alexcarpenter

Copy link
Copy Markdown
MemberAuthor

closing in favor of #8919

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@alexcarpenter@wobsoriano
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(ui): add Mosaic state machine — core library, hook, and tests by alexcarpenter · Pull Request #8947 · clerk/javascript · GitHub
Skip to content

feat(ui): add Mosaic state machine — core library, hook, and tests - #8947

Closed
alexcarpenter wants to merge 3 commits into
mainfrom
feat/mosaic-machine-core
Closed

feat(ui): add Mosaic state machine — core library, hook, and tests#8947
alexcarpenter wants to merge 3 commits into
mainfrom
feat/mosaic-machine-core

Conversation

@alexcarpenter

@alexcarpenteralexcarpenter commented Jun 22, 2026

Copy link
Copy Markdown
Member

Summary

Introduces packages/ui/src/mosaic/machine: a tiny, dependency-free state machine runtime for modelling multi-step UI flows (confirm dialogs, sign-in wizards, async operations) without XState or any external dependency.

  • createMachine — define states, transitions, guards, and async invocations with full TypeScript inference
  • createActor — runtime that drives the machine; restarts cleanly through React StrictMode double-mount
  • setup() — typed builder with after (delayed transitions) and fromPromise (typed async invoke)
  • assign — immutable context updater helper
  • useMachine / useActor / useSelector — React bindings with stale-closure protection and setContext for syncing external context changes

Tests

  • machine.test.ts — transitions, guards, context, error states
  • useMachine.test.tsx — StrictMode behaviour, setContext, onDone
  • patterns.test.ts — common usage patterns
  • types.test-d.ts — compile-time type assertions
  • wizard-migration.test.tsx — full before/after for migrating a hand-rolled reducer flow

Stack

  1. This PR — core library
  2. feat/mosaic-machine-sections — delete/leave-org section migrations
  3. feat/mosaic-machine-signin — sign-in flow machines + adoption guide

Test plan

  • turbo test --filter @clerk/ui passes on this branch in isolation

Summary by CodeRabbit

  • New Features

    • New Mosaic state machine system for UI state, transitions, guards, async invokes, and actor runtime control
    • Added React hooks integration: useMachine, useActor, useSelector, plus optional transition logging
    • Added helper APIs for building typed machines, including setup, createMachine, assign, and promise-based invokes
    • Added actor utilities, including snapshot “teleporting” via a mock actor
  • Documentation

    • Updated and expanded guides with “first machine” examples and end-to-end migration walkthroughs
    • Added skill guidance for authoring Mosaic-style machines
  • Tests

    • Extensive new test coverage for runtime behavior, hooks, typing, and wizard/pattern parity

Introduces packages/ui/src/mosaic/machine: a tiny, dependency-free
state machine runtime for modelling multi-step flows in the Mosaic UI.
- createMachine / createActor — define states, transitions, and async
invocations (fromPromise) with full TypeScript inference
- setup() — typed builder with after (delayed transitions) and context
helpers
- assign — immutable context updater
- useMachine / useActor / useSelector — React bindings with StrictMode
safety, stale-closure protection, and setContext for syncing external
context changes
- types.test-d.ts + three test suites covering the full API and the
wizard-migration pattern
@changeset-bot

changeset-botBot commented Jun 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: de57772

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Jun 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJun 22, 2026 7:18pm
swingsetReadyReadyPreview, CommentJun 22, 2026 7:18pm

Request Review

@coderabbitai

coderabbitaiBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

An error occurred during the review process. Please try again later.

📝 Walkthrough

Walkthrough

Introduces a new dependency-free, XState v5-style state machine runtime (packages/ui/src/mosaic/machine) including a TypeScript type system, createMachine/assign/setup factories, a full createActor runtime with async invoke/after-timer/recheck support, React hooks (useMachine, useActor, useSelector), comprehensive Vitest tests, and a documentation README plus Claude SKILL guide.

Changes

Mosaic State Machine Library

Layer / File(s)Summary
Type system and contracts
packages/ui/src/mosaic/machine/types.ts
Defines all shared interfaces, types, and constants: event shapes, guard/action primitives, TransitionConfig, invoke/after event types, StateConfig, MachineConfig, StateMachine, Actor interface, Snapshot, and CreateActorOptions.
assign, createMachine, and setup factories
packages/ui/src/mosaic/machine/assign.ts, packages/ui/src/mosaic/machine/createMachine.ts, packages/ui/src/mosaic/machine/setup.ts
Implements assign action creator with isAssignAction guard, createMachine descriptor factory, and setup<TContext, TEvent>() that pre-binds generics for createMachine, assign, and typed fromPromise.
Actor runtime
packages/ui/src/mosaic/machine/createActor.ts
Full actor lifecycle: state entry/exit with action sequencing, async invoke with cancellation tokens, after timer scheduling/cancellation, guarded always transitions, public API (start, stop, send, can, setContext, recheck), snapshot caching/notification, and mockActor for inert teleported initialization.
React hooks
packages/ui/src/mosaic/machine/useMachine.ts
Implements useActor (via useSyncExternalStore), useMachine (actor ownership with lifecycle, live context injection via useLayoutEffect, onDone callback), useSelector (memoized projection with custom equality), and useMachineLogger.
TypeScript type-safety tests
packages/ui/src/mosaic/machine/types.test-d.ts
Verifies compile-time constraints via expectTypeOf and @ts-expect-error: event-union narrowing on send/can, snapshot shape, assign updater signatures, createMachine state-key constraints, setup pre-binding, and fromPromise typed output inference.
Actor runtime unit tests
packages/ui/src/mosaic/machine/__tests__/machine.test.ts
Covers machine introspection, pre-start safety, transitions/guards/actions, always, async invoke, final states, recheck(), observable contract, context initialization precedence, mockActor, pure helpers, StrictMode restart, and after-timer behavior with fake timers.
Real-world UI pattern tests
packages/ui/src/mosaic/machine/__tests__/patterns.test.ts
Verifies eight Clerk UI coordination patterns: multi-entry loading, resend cooldown, error-clear-on-input, conditional initial state, post-invoke routing, typed fromPromise output, on-mount async invoke, and reactive value auto-switch.
React hook and wizard migration tests
packages/ui/src/mosaic/machine/__tests__/useMachine.test.tsx, packages/ui/src/mosaic/machine/__tests__/wizard-migration.test.tsx
Tests useMachine/useActor/useSelector integration (context injection, teleport, re-render scoping, onDone, live context). Builds and tests a createWizardMachine covering derived initial, NEXT/PREV/GOTO navigation, deriveStepper introspection, recheck() re-seating, boundary-signal proxy, and mockActor gated-step teleport.
Documentation and changeset
packages/ui/src/mosaic/machine/README.md, .claude/skills/mosaic-machine/SKILL.md, .changeset/mosaic-state-machine.md
Full machine README with first-machine walkthrough, building blocks, API table, no-op/recheck semantics, and ConfigureSSO Wizard migration example. Claude SKILL guide with do/don't conventions, setup typing guide, and React consumption patterns. Changeset version marker.

Sequence Diagram(s)

sequenceDiagram
participant UI
participant Hook
participant Runtime
participant Machine
UI->>Hook: mount with machine and options
Hook->>Runtime: createActor
Runtime->>Machine: resolve initial state and context
Hook->>Runtime: start
UI->>Hook: send event
Hook->>Runtime: send
Runtime->>Machine: pick and take transition
Runtime-->>Hook: publish snapshot
Hook-->>UI: render with snapshot
UI->>Hook: unmount
Hook->>Runtime: stop
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Suggested labels

core-3

Suggested reviewers

  • brkalow
  • Ephem

Poem

🐇 Hop, hop! A machine is born today,
States and events to light the way.
assign patches context neat and clean,
always guards flow through the in-between.
useMachine mounts, then stop() on leave —
A rabbit-built runtime you can trust and believe! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 40.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat(ui): add Mosaic state machine — core library, hook, and tests' accurately describes the main change: introduction of a new state machine library with core APIs, React hooks, and comprehensive test coverage for managing multi-step UI flows.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new

pkg-pr-newBot commented Jun 22, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8947

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8947

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8947

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8947

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@8947

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8947

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8947

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8947

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8947

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8947

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8947

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8947

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8947

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8947

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8947

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8947

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8947

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8947

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8947

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8947

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8947

commit: de57772

@github-actions

github-actionsBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-22T19:19:47.984Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on de57772.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
packages/ui/src/mosaic/machine/setup.ts (1)

77-82: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Prefer a typed wrapper over double-casting in fromPromise (Line 81).

fn as unknown as ... hides contract drift. A tiny adapter preserves the same runtime behavior and keeps type checking honest.

💡 Proposed change
- src: fn as unknown as InvokeConfig<TContext, TEvent, TOutput, TStates>['src'],+ src: (context, _event) => fn(context),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/mosaic/machine/setup.ts` around lines 77 - 82, The
double-casting pattern in the src property assignment within fromPromise hides
type contract drift and should be replaced with a typed wrapper function. Create
a wrapper function that takes the context and event parameters (matching
InvokeConfig src signature) but only passes the context parameter to the
original fn, discarding the event parameter. This wrapper preserves the exact
same runtime behavior while maintaining proper type safety without hidden casts.
Assign this wrapper function to src instead of using the double-cast pattern.
packages/ui/src/mosaic/machine/__tests__/machine.test.ts (1)

220-314: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add two invoke regression tests: sync-throw and guard-blocked completion no-op.

The invoke suite is strong, but it should also pin:

  1. invoke.src synchronous throw routes via onError (no actor crash), and
  2. onDone/onError transition blocked by target entry guard is a true no-op (no notify/ref churn).

As per coding guidelines, “Unit tests are required for all new functionality” and “Verify proper error handling and edge cases.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/mosaic/machine/__tests__/machine.test.ts` around lines 220 -
314, Add two new test cases to the existing describe block 'createActor — invoke
(async)'. First, add a test that verifies when invoke.src throws synchronously,
the error is properly routed to onError and the actor does not crash. Second,
add a test that verifies when an onDone or onError transition has a target with
an entry guard that blocks the transition, it results in a true no-op without
any notification or reference management churn. Both tests should follow the
same pattern as the existing tests in the suite, using createMachine,
createActor, and tick utilities to verify the expected behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/ui/src/mosaic/machine/createActor.ts`:
- Around line 145-154: The error handler for invoke.onError is calling commit()
unconditionally after takeTransition(), but takeTransition() can return false
when a transition is blocked by an entry guard. Modify the code to check the
return value of takeTransition() and only call commit() if it returns true. This
same pattern should also be applied to the onDone handler above this code block
to ensure no unnecessary commits occur on blocked transitions.
- Around line 139-156: The current Promise.resolve(invoke.src(context, event as
never)) eagerly evaluates the invoke.src call, which means synchronous errors
thrown from invoke.src escape the promise chain instead of being caught and
routed to the onError handler. To fix this, wrap the invoke.src call in a
deferred microtask by changing Promise.resolve(invoke.src(...)) to
Promise.resolve().then(() => invoke.src(...)) so that both synchronous and
asynchronous errors are consistently caught by the error rejection handler that
calls the onError transition.
In `@packages/ui/src/mosaic/machine/setup.ts`:
- Around line 41-85: The exported setup function at line 41 lacks an explicit
return type annotation, which violates the repository's TypeScript guidelines
for public APIs. Add an explicit return type to the setup function that
describes the object structure it returns, which includes the createMachine,
assign, and fromPromise methods with their respective signatures. The return
type should be defined immediately after the function's closing parenthesis and
before the opening brace of the function body.
---
Nitpick comments:
In `@packages/ui/src/mosaic/machine/__tests__/machine.test.ts`:
- Around line 220-314: Add two new test cases to the existing describe block
'createActor — invoke (async)'. First, add a test that verifies when invoke.src
throws synchronously, the error is properly routed to onError and the actor does
not crash. Second, add a test that verifies when an onDone or onError transition
has a target with an entry guard that blocks the transition, it results in a
true no-op without any notification or reference management churn. Both tests
should follow the same pattern as the existing tests in the suite, using
createMachine, createActor, and tick utilities to verify the expected behavior.
In `@packages/ui/src/mosaic/machine/setup.ts`:
- Around line 77-82: The double-casting pattern in the src property assignment
within fromPromise hides type contract drift and should be replaced with a typed
wrapper function. Create a wrapper function that takes the context and event
parameters (matching InvokeConfig src signature) but only passes the context
parameter to the original fn, discarding the event parameter. This wrapper
preserves the exact same runtime behavior while maintaining proper type safety
without hidden casts. Assign this wrapper function to src instead of using the
double-cast pattern.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: d5481399-00a3-4632-9c85-99189d93cb08

📥 Commits

Reviewing files that changed from the base of the PR and between 7e3174a and dce39c1.

📒 Files selected for processing (14)
  • .changeset/mosaic-state-machine.md
  • .claude/skills/mosaic-machine/SKILL.md
  • packages/ui/src/mosaic/machine/README.md
  • packages/ui/src/mosaic/machine/__tests__/machine.test.ts
  • packages/ui/src/mosaic/machine/__tests__/patterns.test.ts
  • packages/ui/src/mosaic/machine/__tests__/useMachine.test.tsx
  • packages/ui/src/mosaic/machine/__tests__/wizard-migration.test.tsx
  • packages/ui/src/mosaic/machine/assign.ts
  • packages/ui/src/mosaic/machine/createActor.ts
  • packages/ui/src/mosaic/machine/createMachine.ts
  • packages/ui/src/mosaic/machine/setup.ts
  • packages/ui/src/mosaic/machine/types.test-d.ts
  • packages/ui/src/mosaic/machine/types.ts
  • packages/ui/src/mosaic/machine/useMachine.ts

Comment threadpackages/ui/src/mosaic/machine/createActor.ts Outdated
Comment threadpackages/ui/src/mosaic/machine/createActor.ts Outdated
Comment threadpackages/ui/src/mosaic/machine/setup.ts Outdated
- createActor: wrap invoke src in Promise constructor so a synchronous
throw routes to onError rather than escaping as an unhandled exception
(Promise.resolve(fn()) evaluates fn() before the chain begins)
- createActor: guard commit() on takeTransition return value for
onDone/onError handlers, matching the existing after-timer pattern
- setup: add SetupTools interface and explicit return type annotation
Promise.resolve(src()) returns the same native Promise with zero extra
microtask hops; the previous new Promise(r => r(thenable)) added two
hops via the thenable resolution procedure, causing the cooldown-timer
tests to read state before onDone had fired.
Add delete-organization-machine.ts test fixture to the core branch so
machine.test.ts and useMachine.test.tsx compile without a dep on the
sections layer.
@alexcarpenter

Copy link
Copy Markdown
MemberAuthor

closing in favor of #8919

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@alexcarpenter@wobsoriano
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' feat(ui): add Mosaic state machine — core library, hook, and tests by alexcarpenter · Pull Request #8947 · clerk/javascript · GitHub
Skip to content

feat(ui): add Mosaic state machine — core library, hook, and tests - #8947

Closed
alexcarpenter wants to merge 3 commits into
mainfrom
feat/mosaic-machine-core
Closed

feat(ui): add Mosaic state machine — core library, hook, and tests#8947
alexcarpenter wants to merge 3 commits into
mainfrom
feat/mosaic-machine-core

Conversation

@alexcarpenter

@alexcarpenteralexcarpenter commented Jun 22, 2026

Copy link
Copy Markdown
Member

Summary

Introduces packages/ui/src/mosaic/machine: a tiny, dependency-free state machine runtime for modelling multi-step UI flows (confirm dialogs, sign-in wizards, async operations) without XState or any external dependency.

  • createMachine — define states, transitions, guards, and async invocations with full TypeScript inference
  • createActor — runtime that drives the machine; restarts cleanly through React StrictMode double-mount
  • setup() — typed builder with after (delayed transitions) and fromPromise (typed async invoke)
  • assign — immutable context updater helper
  • useMachine / useActor / useSelector — React bindings with stale-closure protection and setContext for syncing external context changes

Tests

  • machine.test.ts — transitions, guards, context, error states
  • useMachine.test.tsx — StrictMode behaviour, setContext, onDone
  • patterns.test.ts — common usage patterns
  • types.test-d.ts — compile-time type assertions
  • wizard-migration.test.tsx — full before/after for migrating a hand-rolled reducer flow

Stack

  1. This PR — core library
  2. feat/mosaic-machine-sections — delete/leave-org section migrations
  3. feat/mosaic-machine-signin — sign-in flow machines + adoption guide

Test plan

  • turbo test --filter @clerk/ui passes on this branch in isolation

Summary by CodeRabbit

  • New Features

    • New Mosaic state machine system for UI state, transitions, guards, async invokes, and actor runtime control
    • Added React hooks integration: useMachine, useActor, useSelector, plus optional transition logging
    • Added helper APIs for building typed machines, including setup, createMachine, assign, and promise-based invokes
    • Added actor utilities, including snapshot “teleporting” via a mock actor
  • Documentation

    • Updated and expanded guides with “first machine” examples and end-to-end migration walkthroughs
    • Added skill guidance for authoring Mosaic-style machines
  • Tests

    • Extensive new test coverage for runtime behavior, hooks, typing, and wizard/pattern parity

Introduces packages/ui/src/mosaic/machine: a tiny, dependency-free
state machine runtime for modelling multi-step flows in the Mosaic UI.
- createMachine / createActor — define states, transitions, and async
invocations (fromPromise) with full TypeScript inference
- setup() — typed builder with after (delayed transitions) and context
helpers
- assign — immutable context updater
- useMachine / useActor / useSelector — React bindings with StrictMode
safety, stale-closure protection, and setContext for syncing external
context changes
- types.test-d.ts + three test suites covering the full API and the
wizard-migration pattern
@changeset-bot

changeset-botBot commented Jun 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: de57772

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Jun 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJun 22, 2026 7:18pm
swingsetReadyReadyPreview, CommentJun 22, 2026 7:18pm

Request Review

@coderabbitai

coderabbitaiBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

An error occurred during the review process. Please try again later.

📝 Walkthrough

Walkthrough

Introduces a new dependency-free, XState v5-style state machine runtime (packages/ui/src/mosaic/machine) including a TypeScript type system, createMachine/assign/setup factories, a full createActor runtime with async invoke/after-timer/recheck support, React hooks (useMachine, useActor, useSelector), comprehensive Vitest tests, and a documentation README plus Claude SKILL guide.

Changes

Mosaic State Machine Library

Layer / File(s)Summary
Type system and contracts
packages/ui/src/mosaic/machine/types.ts
Defines all shared interfaces, types, and constants: event shapes, guard/action primitives, TransitionConfig, invoke/after event types, StateConfig, MachineConfig, StateMachine, Actor interface, Snapshot, and CreateActorOptions.
assign, createMachine, and setup factories
packages/ui/src/mosaic/machine/assign.ts, packages/ui/src/mosaic/machine/createMachine.ts, packages/ui/src/mosaic/machine/setup.ts
Implements assign action creator with isAssignAction guard, createMachine descriptor factory, and setup<TContext, TEvent>() that pre-binds generics for createMachine, assign, and typed fromPromise.
Actor runtime
packages/ui/src/mosaic/machine/createActor.ts
Full actor lifecycle: state entry/exit with action sequencing, async invoke with cancellation tokens, after timer scheduling/cancellation, guarded always transitions, public API (start, stop, send, can, setContext, recheck), snapshot caching/notification, and mockActor for inert teleported initialization.
React hooks
packages/ui/src/mosaic/machine/useMachine.ts
Implements useActor (via useSyncExternalStore), useMachine (actor ownership with lifecycle, live context injection via useLayoutEffect, onDone callback), useSelector (memoized projection with custom equality), and useMachineLogger.
TypeScript type-safety tests
packages/ui/src/mosaic/machine/types.test-d.ts
Verifies compile-time constraints via expectTypeOf and @ts-expect-error: event-union narrowing on send/can, snapshot shape, assign updater signatures, createMachine state-key constraints, setup pre-binding, and fromPromise typed output inference.
Actor runtime unit tests
packages/ui/src/mosaic/machine/__tests__/machine.test.ts
Covers machine introspection, pre-start safety, transitions/guards/actions, always, async invoke, final states, recheck(), observable contract, context initialization precedence, mockActor, pure helpers, StrictMode restart, and after-timer behavior with fake timers.
Real-world UI pattern tests
packages/ui/src/mosaic/machine/__tests__/patterns.test.ts
Verifies eight Clerk UI coordination patterns: multi-entry loading, resend cooldown, error-clear-on-input, conditional initial state, post-invoke routing, typed fromPromise output, on-mount async invoke, and reactive value auto-switch.
React hook and wizard migration tests
packages/ui/src/mosaic/machine/__tests__/useMachine.test.tsx, packages/ui/src/mosaic/machine/__tests__/wizard-migration.test.tsx
Tests useMachine/useActor/useSelector integration (context injection, teleport, re-render scoping, onDone, live context). Builds and tests a createWizardMachine covering derived initial, NEXT/PREV/GOTO navigation, deriveStepper introspection, recheck() re-seating, boundary-signal proxy, and mockActor gated-step teleport.
Documentation and changeset
packages/ui/src/mosaic/machine/README.md, .claude/skills/mosaic-machine/SKILL.md, .changeset/mosaic-state-machine.md
Full machine README with first-machine walkthrough, building blocks, API table, no-op/recheck semantics, and ConfigureSSO Wizard migration example. Claude SKILL guide with do/don't conventions, setup typing guide, and React consumption patterns. Changeset version marker.

Sequence Diagram(s)

sequenceDiagram
participant UI
participant Hook
participant Runtime
participant Machine
UI->>Hook: mount with machine and options
Hook->>Runtime: createActor
Runtime->>Machine: resolve initial state and context
Hook->>Runtime: start
UI->>Hook: send event
Hook->>Runtime: send
Runtime->>Machine: pick and take transition
Runtime-->>Hook: publish snapshot
Hook-->>UI: render with snapshot
UI->>Hook: unmount
Hook->>Runtime: stop
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Suggested labels

core-3

Suggested reviewers

  • brkalow
  • Ephem

Poem

🐇 Hop, hop! A machine is born today,
States and events to light the way.
assign patches context neat and clean,
always guards flow through the in-between.
useMachine mounts, then stop() on leave —
A rabbit-built runtime you can trust and believe! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 40.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat(ui): add Mosaic state machine — core library, hook, and tests' accurately describes the main change: introduction of a new state machine library with core APIs, React hooks, and comprehensive test coverage for managing multi-step UI flows.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new

pkg-pr-newBot commented Jun 22, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8947

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8947

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8947

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8947

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@8947

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8947

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8947

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8947

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8947

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8947

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8947

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8947

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8947

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8947

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8947

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8947

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8947

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8947

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8947

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8947

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8947

commit: de57772

@github-actions

github-actionsBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-22T19:19:47.984Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on de57772.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
packages/ui/src/mosaic/machine/setup.ts (1)

77-82: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Prefer a typed wrapper over double-casting in fromPromise (Line 81).

fn as unknown as ... hides contract drift. A tiny adapter preserves the same runtime behavior and keeps type checking honest.

💡 Proposed change
- src: fn as unknown as InvokeConfig<TContext, TEvent, TOutput, TStates>['src'],+ src: (context, _event) => fn(context),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/mosaic/machine/setup.ts` around lines 77 - 82, The
double-casting pattern in the src property assignment within fromPromise hides
type contract drift and should be replaced with a typed wrapper function. Create
a wrapper function that takes the context and event parameters (matching
InvokeConfig src signature) but only passes the context parameter to the
original fn, discarding the event parameter. This wrapper preserves the exact
same runtime behavior while maintaining proper type safety without hidden casts.
Assign this wrapper function to src instead of using the double-cast pattern.
packages/ui/src/mosaic/machine/__tests__/machine.test.ts (1)

220-314: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add two invoke regression tests: sync-throw and guard-blocked completion no-op.

The invoke suite is strong, but it should also pin:

  1. invoke.src synchronous throw routes via onError (no actor crash), and
  2. onDone/onError transition blocked by target entry guard is a true no-op (no notify/ref churn).

As per coding guidelines, “Unit tests are required for all new functionality” and “Verify proper error handling and edge cases.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/mosaic/machine/__tests__/machine.test.ts` around lines 220 -
314, Add two new test cases to the existing describe block 'createActor — invoke
(async)'. First, add a test that verifies when invoke.src throws synchronously,
the error is properly routed to onError and the actor does not crash. Second,
add a test that verifies when an onDone or onError transition has a target with
an entry guard that blocks the transition, it results in a true no-op without
any notification or reference management churn. Both tests should follow the
same pattern as the existing tests in the suite, using createMachine,
createActor, and tick utilities to verify the expected behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/ui/src/mosaic/machine/createActor.ts`:
- Around line 145-154: The error handler for invoke.onError is calling commit()
unconditionally after takeTransition(), but takeTransition() can return false
when a transition is blocked by an entry guard. Modify the code to check the
return value of takeTransition() and only call commit() if it returns true. This
same pattern should also be applied to the onDone handler above this code block
to ensure no unnecessary commits occur on blocked transitions.
- Around line 139-156: The current Promise.resolve(invoke.src(context, event as
never)) eagerly evaluates the invoke.src call, which means synchronous errors
thrown from invoke.src escape the promise chain instead of being caught and
routed to the onError handler. To fix this, wrap the invoke.src call in a
deferred microtask by changing Promise.resolve(invoke.src(...)) to
Promise.resolve().then(() => invoke.src(...)) so that both synchronous and
asynchronous errors are consistently caught by the error rejection handler that
calls the onError transition.
In `@packages/ui/src/mosaic/machine/setup.ts`:
- Around line 41-85: The exported setup function at line 41 lacks an explicit
return type annotation, which violates the repository's TypeScript guidelines
for public APIs. Add an explicit return type to the setup function that
describes the object structure it returns, which includes the createMachine,
assign, and fromPromise methods with their respective signatures. The return
type should be defined immediately after the function's closing parenthesis and
before the opening brace of the function body.
---
Nitpick comments:
In `@packages/ui/src/mosaic/machine/__tests__/machine.test.ts`:
- Around line 220-314: Add two new test cases to the existing describe block
'createActor — invoke (async)'. First, add a test that verifies when invoke.src
throws synchronously, the error is properly routed to onError and the actor does
not crash. Second, add a test that verifies when an onDone or onError transition
has a target with an entry guard that blocks the transition, it results in a
true no-op without any notification or reference management churn. Both tests
should follow the same pattern as the existing tests in the suite, using
createMachine, createActor, and tick utilities to verify the expected behavior.
In `@packages/ui/src/mosaic/machine/setup.ts`:
- Around line 77-82: The double-casting pattern in the src property assignment
within fromPromise hides type contract drift and should be replaced with a typed
wrapper function. Create a wrapper function that takes the context and event
parameters (matching InvokeConfig src signature) but only passes the context
parameter to the original fn, discarding the event parameter. This wrapper
preserves the exact same runtime behavior while maintaining proper type safety
without hidden casts. Assign this wrapper function to src instead of using the
double-cast pattern.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: d5481399-00a3-4632-9c85-99189d93cb08

📥 Commits

Reviewing files that changed from the base of the PR and between 7e3174a and dce39c1.

📒 Files selected for processing (14)
  • .changeset/mosaic-state-machine.md
  • .claude/skills/mosaic-machine/SKILL.md
  • packages/ui/src/mosaic/machine/README.md
  • packages/ui/src/mosaic/machine/__tests__/machine.test.ts
  • packages/ui/src/mosaic/machine/__tests__/patterns.test.ts
  • packages/ui/src/mosaic/machine/__tests__/useMachine.test.tsx
  • packages/ui/src/mosaic/machine/__tests__/wizard-migration.test.tsx
  • packages/ui/src/mosaic/machine/assign.ts
  • packages/ui/src/mosaic/machine/createActor.ts
  • packages/ui/src/mosaic/machine/createMachine.ts
  • packages/ui/src/mosaic/machine/setup.ts
  • packages/ui/src/mosaic/machine/types.test-d.ts
  • packages/ui/src/mosaic/machine/types.ts
  • packages/ui/src/mosaic/machine/useMachine.ts

Comment threadpackages/ui/src/mosaic/machine/createActor.ts Outdated
Comment threadpackages/ui/src/mosaic/machine/createActor.ts Outdated
Comment threadpackages/ui/src/mosaic/machine/setup.ts Outdated
- createActor: wrap invoke src in Promise constructor so a synchronous
throw routes to onError rather than escaping as an unhandled exception
(Promise.resolve(fn()) evaluates fn() before the chain begins)
- createActor: guard commit() on takeTransition return value for
onDone/onError handlers, matching the existing after-timer pattern
- setup: add SetupTools interface and explicit return type annotation
Promise.resolve(src()) returns the same native Promise with zero extra
microtask hops; the previous new Promise(r => r(thenable)) added two
hops via the thenable resolution procedure, causing the cooldown-timer
tests to read state before onDone had fired.
Add delete-organization-machine.ts test fixture to the core branch so
machine.test.ts and useMachine.test.tsx compile without a dep on the
sections layer.
@alexcarpenter

Copy link
Copy Markdown
MemberAuthor

closing in favor of #8919

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@alexcarpenter@wobsoriano
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(ui): add Mosaic state machine — core library, hook, and tests by alexcarpenter · Pull Request #8947 · clerk/javascript · GitHub
Skip to content

feat(ui): add Mosaic state machine — core library, hook, and tests - #8947

Closed
alexcarpenter wants to merge 3 commits into
mainfrom
feat/mosaic-machine-core
Closed

feat(ui): add Mosaic state machine — core library, hook, and tests#8947
alexcarpenter wants to merge 3 commits into
mainfrom
feat/mosaic-machine-core

Conversation

@alexcarpenter

@alexcarpenteralexcarpenter commented Jun 22, 2026

Copy link
Copy Markdown
Member

Summary

Introduces packages/ui/src/mosaic/machine: a tiny, dependency-free state machine runtime for modelling multi-step UI flows (confirm dialogs, sign-in wizards, async operations) without XState or any external dependency.

  • createMachine — define states, transitions, guards, and async invocations with full TypeScript inference
  • createActor — runtime that drives the machine; restarts cleanly through React StrictMode double-mount
  • setup() — typed builder with after (delayed transitions) and fromPromise (typed async invoke)
  • assign — immutable context updater helper
  • useMachine / useActor / useSelector — React bindings with stale-closure protection and setContext for syncing external context changes

Tests

  • machine.test.ts — transitions, guards, context, error states
  • useMachine.test.tsx — StrictMode behaviour, setContext, onDone
  • patterns.test.ts — common usage patterns
  • types.test-d.ts — compile-time type assertions
  • wizard-migration.test.tsx — full before/after for migrating a hand-rolled reducer flow

Stack

  1. This PR — core library
  2. feat/mosaic-machine-sections — delete/leave-org section migrations
  3. feat/mosaic-machine-signin — sign-in flow machines + adoption guide

Test plan

  • turbo test --filter @clerk/ui passes on this branch in isolation

Summary by CodeRabbit

  • New Features

    • New Mosaic state machine system for UI state, transitions, guards, async invokes, and actor runtime control
    • Added React hooks integration: useMachine, useActor, useSelector, plus optional transition logging
    • Added helper APIs for building typed machines, including setup, createMachine, assign, and promise-based invokes
    • Added actor utilities, including snapshot “teleporting” via a mock actor
  • Documentation

    • Updated and expanded guides with “first machine” examples and end-to-end migration walkthroughs
    • Added skill guidance for authoring Mosaic-style machines
  • Tests

    • Extensive new test coverage for runtime behavior, hooks, typing, and wizard/pattern parity

Introduces packages/ui/src/mosaic/machine: a tiny, dependency-free
state machine runtime for modelling multi-step flows in the Mosaic UI.
- createMachine / createActor — define states, transitions, and async
invocations (fromPromise) with full TypeScript inference
- setup() — typed builder with after (delayed transitions) and context
helpers
- assign — immutable context updater
- useMachine / useActor / useSelector — React bindings with StrictMode
safety, stale-closure protection, and setContext for syncing external
context changes
- types.test-d.ts + three test suites covering the full API and the
wizard-migration pattern
@changeset-bot

changeset-botBot commented Jun 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: de57772

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Jun 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJun 22, 2026 7:18pm
swingsetReadyReadyPreview, CommentJun 22, 2026 7:18pm

Request Review

@coderabbitai

coderabbitaiBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

An error occurred during the review process. Please try again later.

📝 Walkthrough

Walkthrough

Introduces a new dependency-free, XState v5-style state machine runtime (packages/ui/src/mosaic/machine) including a TypeScript type system, createMachine/assign/setup factories, a full createActor runtime with async invoke/after-timer/recheck support, React hooks (useMachine, useActor, useSelector), comprehensive Vitest tests, and a documentation README plus Claude SKILL guide.

Changes

Mosaic State Machine Library

Layer / File(s)Summary
Type system and contracts
packages/ui/src/mosaic/machine/types.ts
Defines all shared interfaces, types, and constants: event shapes, guard/action primitives, TransitionConfig, invoke/after event types, StateConfig, MachineConfig, StateMachine, Actor interface, Snapshot, and CreateActorOptions.
assign, createMachine, and setup factories
packages/ui/src/mosaic/machine/assign.ts, packages/ui/src/mosaic/machine/createMachine.ts, packages/ui/src/mosaic/machine/setup.ts
Implements assign action creator with isAssignAction guard, createMachine descriptor factory, and setup<TContext, TEvent>() that pre-binds generics for createMachine, assign, and typed fromPromise.
Actor runtime
packages/ui/src/mosaic/machine/createActor.ts
Full actor lifecycle: state entry/exit with action sequencing, async invoke with cancellation tokens, after timer scheduling/cancellation, guarded always transitions, public API (start, stop, send, can, setContext, recheck), snapshot caching/notification, and mockActor for inert teleported initialization.
React hooks
packages/ui/src/mosaic/machine/useMachine.ts
Implements useActor (via useSyncExternalStore), useMachine (actor ownership with lifecycle, live context injection via useLayoutEffect, onDone callback), useSelector (memoized projection with custom equality), and useMachineLogger.
TypeScript type-safety tests
packages/ui/src/mosaic/machine/types.test-d.ts
Verifies compile-time constraints via expectTypeOf and @ts-expect-error: event-union narrowing on send/can, snapshot shape, assign updater signatures, createMachine state-key constraints, setup pre-binding, and fromPromise typed output inference.
Actor runtime unit tests
packages/ui/src/mosaic/machine/__tests__/machine.test.ts
Covers machine introspection, pre-start safety, transitions/guards/actions, always, async invoke, final states, recheck(), observable contract, context initialization precedence, mockActor, pure helpers, StrictMode restart, and after-timer behavior with fake timers.
Real-world UI pattern tests
packages/ui/src/mosaic/machine/__tests__/patterns.test.ts
Verifies eight Clerk UI coordination patterns: multi-entry loading, resend cooldown, error-clear-on-input, conditional initial state, post-invoke routing, typed fromPromise output, on-mount async invoke, and reactive value auto-switch.
React hook and wizard migration tests
packages/ui/src/mosaic/machine/__tests__/useMachine.test.tsx, packages/ui/src/mosaic/machine/__tests__/wizard-migration.test.tsx
Tests useMachine/useActor/useSelector integration (context injection, teleport, re-render scoping, onDone, live context). Builds and tests a createWizardMachine covering derived initial, NEXT/PREV/GOTO navigation, deriveStepper introspection, recheck() re-seating, boundary-signal proxy, and mockActor gated-step teleport.
Documentation and changeset
packages/ui/src/mosaic/machine/README.md, .claude/skills/mosaic-machine/SKILL.md, .changeset/mosaic-state-machine.md
Full machine README with first-machine walkthrough, building blocks, API table, no-op/recheck semantics, and ConfigureSSO Wizard migration example. Claude SKILL guide with do/don't conventions, setup typing guide, and React consumption patterns. Changeset version marker.

Sequence Diagram(s)

sequenceDiagram
participant UI
participant Hook
participant Runtime
participant Machine
UI->>Hook: mount with machine and options
Hook->>Runtime: createActor
Runtime->>Machine: resolve initial state and context
Hook->>Runtime: start
UI->>Hook: send event
Hook->>Runtime: send
Runtime->>Machine: pick and take transition
Runtime-->>Hook: publish snapshot
Hook-->>UI: render with snapshot
UI->>Hook: unmount
Hook->>Runtime: stop
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Suggested labels

core-3

Suggested reviewers

  • brkalow
  • Ephem

Poem

🐇 Hop, hop! A machine is born today,
States and events to light the way.
assign patches context neat and clean,
always guards flow through the in-between.
useMachine mounts, then stop() on leave —
A rabbit-built runtime you can trust and believe! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 40.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat(ui): add Mosaic state machine — core library, hook, and tests' accurately describes the main change: introduction of a new state machine library with core APIs, React hooks, and comprehensive test coverage for managing multi-step UI flows.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new

pkg-pr-newBot commented Jun 22, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8947

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8947

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8947

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8947

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@8947

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8947

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8947

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8947

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8947

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8947

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8947

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8947

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8947

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8947

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8947

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8947

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8947

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8947

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8947

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8947

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8947

commit: de57772

@github-actions

github-actionsBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-22T19:19:47.984Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on de57772.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
packages/ui/src/mosaic/machine/setup.ts (1)

77-82: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Prefer a typed wrapper over double-casting in fromPromise (Line 81).

fn as unknown as ... hides contract drift. A tiny adapter preserves the same runtime behavior and keeps type checking honest.

💡 Proposed change
- src: fn as unknown as InvokeConfig<TContext, TEvent, TOutput, TStates>['src'],+ src: (context, _event) => fn(context),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/mosaic/machine/setup.ts` around lines 77 - 82, The
double-casting pattern in the src property assignment within fromPromise hides
type contract drift and should be replaced with a typed wrapper function. Create
a wrapper function that takes the context and event parameters (matching
InvokeConfig src signature) but only passes the context parameter to the
original fn, discarding the event parameter. This wrapper preserves the exact
same runtime behavior while maintaining proper type safety without hidden casts.
Assign this wrapper function to src instead of using the double-cast pattern.
packages/ui/src/mosaic/machine/__tests__/machine.test.ts (1)

220-314: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add two invoke regression tests: sync-throw and guard-blocked completion no-op.

The invoke suite is strong, but it should also pin:

  1. invoke.src synchronous throw routes via onError (no actor crash), and
  2. onDone/onError transition blocked by target entry guard is a true no-op (no notify/ref churn).

As per coding guidelines, “Unit tests are required for all new functionality” and “Verify proper error handling and edge cases.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/mosaic/machine/__tests__/machine.test.ts` around lines 220 -
314, Add two new test cases to the existing describe block 'createActor — invoke
(async)'. First, add a test that verifies when invoke.src throws synchronously,
the error is properly routed to onError and the actor does not crash. Second,
add a test that verifies when an onDone or onError transition has a target with
an entry guard that blocks the transition, it results in a true no-op without
any notification or reference management churn. Both tests should follow the
same pattern as the existing tests in the suite, using createMachine,
createActor, and tick utilities to verify the expected behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/ui/src/mosaic/machine/createActor.ts`:
- Around line 145-154: The error handler for invoke.onError is calling commit()
unconditionally after takeTransition(), but takeTransition() can return false
when a transition is blocked by an entry guard. Modify the code to check the
return value of takeTransition() and only call commit() if it returns true. This
same pattern should also be applied to the onDone handler above this code block
to ensure no unnecessary commits occur on blocked transitions.
- Around line 139-156: The current Promise.resolve(invoke.src(context, event as
never)) eagerly evaluates the invoke.src call, which means synchronous errors
thrown from invoke.src escape the promise chain instead of being caught and
routed to the onError handler. To fix this, wrap the invoke.src call in a
deferred microtask by changing Promise.resolve(invoke.src(...)) to
Promise.resolve().then(() => invoke.src(...)) so that both synchronous and
asynchronous errors are consistently caught by the error rejection handler that
calls the onError transition.
In `@packages/ui/src/mosaic/machine/setup.ts`:
- Around line 41-85: The exported setup function at line 41 lacks an explicit
return type annotation, which violates the repository's TypeScript guidelines
for public APIs. Add an explicit return type to the setup function that
describes the object structure it returns, which includes the createMachine,
assign, and fromPromise methods with their respective signatures. The return
type should be defined immediately after the function's closing parenthesis and
before the opening brace of the function body.
---
Nitpick comments:
In `@packages/ui/src/mosaic/machine/__tests__/machine.test.ts`:
- Around line 220-314: Add two new test cases to the existing describe block
'createActor — invoke (async)'. First, add a test that verifies when invoke.src
throws synchronously, the error is properly routed to onError and the actor does
not crash. Second, add a test that verifies when an onDone or onError transition
has a target with an entry guard that blocks the transition, it results in a
true no-op without any notification or reference management churn. Both tests
should follow the same pattern as the existing tests in the suite, using
createMachine, createActor, and tick utilities to verify the expected behavior.
In `@packages/ui/src/mosaic/machine/setup.ts`:
- Around line 77-82: The double-casting pattern in the src property assignment
within fromPromise hides type contract drift and should be replaced with a typed
wrapper function. Create a wrapper function that takes the context and event
parameters (matching InvokeConfig src signature) but only passes the context
parameter to the original fn, discarding the event parameter. This wrapper
preserves the exact same runtime behavior while maintaining proper type safety
without hidden casts. Assign this wrapper function to src instead of using the
double-cast pattern.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: d5481399-00a3-4632-9c85-99189d93cb08

📥 Commits

Reviewing files that changed from the base of the PR and between 7e3174a and dce39c1.

📒 Files selected for processing (14)
  • .changeset/mosaic-state-machine.md
  • .claude/skills/mosaic-machine/SKILL.md
  • packages/ui/src/mosaic/machine/README.md
  • packages/ui/src/mosaic/machine/__tests__/machine.test.ts
  • packages/ui/src/mosaic/machine/__tests__/patterns.test.ts
  • packages/ui/src/mosaic/machine/__tests__/useMachine.test.tsx
  • packages/ui/src/mosaic/machine/__tests__/wizard-migration.test.tsx
  • packages/ui/src/mosaic/machine/assign.ts
  • packages/ui/src/mosaic/machine/createActor.ts
  • packages/ui/src/mosaic/machine/createMachine.ts
  • packages/ui/src/mosaic/machine/setup.ts
  • packages/ui/src/mosaic/machine/types.test-d.ts
  • packages/ui/src/mosaic/machine/types.ts
  • packages/ui/src/mosaic/machine/useMachine.ts

Comment threadpackages/ui/src/mosaic/machine/createActor.ts Outdated
Comment threadpackages/ui/src/mosaic/machine/createActor.ts Outdated
Comment threadpackages/ui/src/mosaic/machine/setup.ts Outdated
- createActor: wrap invoke src in Promise constructor so a synchronous
throw routes to onError rather than escaping as an unhandled exception
(Promise.resolve(fn()) evaluates fn() before the chain begins)
- createActor: guard commit() on takeTransition return value for
onDone/onError handlers, matching the existing after-timer pattern
- setup: add SetupTools interface and explicit return type annotation
Promise.resolve(src()) returns the same native Promise with zero extra
microtask hops; the previous new Promise(r => r(thenable)) added two
hops via the thenable resolution procedure, causing the cooldown-timer
tests to read state before onDone had fired.
Add delete-organization-machine.ts test fixture to the core branch so
machine.test.ts and useMachine.test.tsx compile without a dep on the
sections layer.
@alexcarpenter

Copy link
Copy Markdown
MemberAuthor

closing in favor of #8919

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@alexcarpenter@wobsoriano
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(ui): add Mosaic state machine — core library, hook, and tests by alexcarpenter · Pull Request #8947 · clerk/javascript · GitHub
Skip to content

feat(ui): add Mosaic state machine — core library, hook, and tests - #8947

Closed
alexcarpenter wants to merge 3 commits into
mainfrom
feat/mosaic-machine-core
Closed

feat(ui): add Mosaic state machine — core library, hook, and tests#8947
alexcarpenter wants to merge 3 commits into
mainfrom
feat/mosaic-machine-core

Conversation

@alexcarpenter

@alexcarpenteralexcarpenter commented Jun 22, 2026

Copy link
Copy Markdown
Member

Summary

Introduces packages/ui/src/mosaic/machine: a tiny, dependency-free state machine runtime for modelling multi-step UI flows (confirm dialogs, sign-in wizards, async operations) without XState or any external dependency.

  • createMachine — define states, transitions, guards, and async invocations with full TypeScript inference
  • createActor — runtime that drives the machine; restarts cleanly through React StrictMode double-mount
  • setup() — typed builder with after (delayed transitions) and fromPromise (typed async invoke)
  • assign — immutable context updater helper
  • useMachine / useActor / useSelector — React bindings with stale-closure protection and setContext for syncing external context changes

Tests

  • machine.test.ts — transitions, guards, context, error states
  • useMachine.test.tsx — StrictMode behaviour, setContext, onDone
  • patterns.test.ts — common usage patterns
  • types.test-d.ts — compile-time type assertions
  • wizard-migration.test.tsx — full before/after for migrating a hand-rolled reducer flow

Stack

  1. This PR — core library
  2. feat/mosaic-machine-sections — delete/leave-org section migrations
  3. feat/mosaic-machine-signin — sign-in flow machines + adoption guide

Test plan

  • turbo test --filter @clerk/ui passes on this branch in isolation

Summary by CodeRabbit

  • New Features

    • New Mosaic state machine system for UI state, transitions, guards, async invokes, and actor runtime control
    • Added React hooks integration: useMachine, useActor, useSelector, plus optional transition logging
    • Added helper APIs for building typed machines, including setup, createMachine, assign, and promise-based invokes
    • Added actor utilities, including snapshot “teleporting” via a mock actor
  • Documentation

    • Updated and expanded guides with “first machine” examples and end-to-end migration walkthroughs
    • Added skill guidance for authoring Mosaic-style machines
  • Tests

    • Extensive new test coverage for runtime behavior, hooks, typing, and wizard/pattern parity

Introduces packages/ui/src/mosaic/machine: a tiny, dependency-free
state machine runtime for modelling multi-step flows in the Mosaic UI.
- createMachine / createActor — define states, transitions, and async
invocations (fromPromise) with full TypeScript inference
- setup() — typed builder with after (delayed transitions) and context
helpers
- assign — immutable context updater
- useMachine / useActor / useSelector — React bindings with StrictMode
safety, stale-closure protection, and setContext for syncing external
context changes
- types.test-d.ts + three test suites covering the full API and the
wizard-migration pattern
@changeset-bot

changeset-botBot commented Jun 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: de57772

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Jun 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJun 22, 2026 7:18pm
swingsetReadyReadyPreview, CommentJun 22, 2026 7:18pm

Request Review

@coderabbitai

coderabbitaiBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

An error occurred during the review process. Please try again later.

📝 Walkthrough

Walkthrough

Introduces a new dependency-free, XState v5-style state machine runtime (packages/ui/src/mosaic/machine) including a TypeScript type system, createMachine/assign/setup factories, a full createActor runtime with async invoke/after-timer/recheck support, React hooks (useMachine, useActor, useSelector), comprehensive Vitest tests, and a documentation README plus Claude SKILL guide.

Changes

Mosaic State Machine Library

Layer / File(s)Summary
Type system and contracts
packages/ui/src/mosaic/machine/types.ts
Defines all shared interfaces, types, and constants: event shapes, guard/action primitives, TransitionConfig, invoke/after event types, StateConfig, MachineConfig, StateMachine, Actor interface, Snapshot, and CreateActorOptions.
assign, createMachine, and setup factories
packages/ui/src/mosaic/machine/assign.ts, packages/ui/src/mosaic/machine/createMachine.ts, packages/ui/src/mosaic/machine/setup.ts
Implements assign action creator with isAssignAction guard, createMachine descriptor factory, and setup<TContext, TEvent>() that pre-binds generics for createMachine, assign, and typed fromPromise.
Actor runtime
packages/ui/src/mosaic/machine/createActor.ts
Full actor lifecycle: state entry/exit with action sequencing, async invoke with cancellation tokens, after timer scheduling/cancellation, guarded always transitions, public API (start, stop, send, can, setContext, recheck), snapshot caching/notification, and mockActor for inert teleported initialization.
React hooks
packages/ui/src/mosaic/machine/useMachine.ts
Implements useActor (via useSyncExternalStore), useMachine (actor ownership with lifecycle, live context injection via useLayoutEffect, onDone callback), useSelector (memoized projection with custom equality), and useMachineLogger.
TypeScript type-safety tests
packages/ui/src/mosaic/machine/types.test-d.ts
Verifies compile-time constraints via expectTypeOf and @ts-expect-error: event-union narrowing on send/can, snapshot shape, assign updater signatures, createMachine state-key constraints, setup pre-binding, and fromPromise typed output inference.
Actor runtime unit tests
packages/ui/src/mosaic/machine/__tests__/machine.test.ts
Covers machine introspection, pre-start safety, transitions/guards/actions, always, async invoke, final states, recheck(), observable contract, context initialization precedence, mockActor, pure helpers, StrictMode restart, and after-timer behavior with fake timers.
Real-world UI pattern tests
packages/ui/src/mosaic/machine/__tests__/patterns.test.ts
Verifies eight Clerk UI coordination patterns: multi-entry loading, resend cooldown, error-clear-on-input, conditional initial state, post-invoke routing, typed fromPromise output, on-mount async invoke, and reactive value auto-switch.
React hook and wizard migration tests
packages/ui/src/mosaic/machine/__tests__/useMachine.test.tsx, packages/ui/src/mosaic/machine/__tests__/wizard-migration.test.tsx
Tests useMachine/useActor/useSelector integration (context injection, teleport, re-render scoping, onDone, live context). Builds and tests a createWizardMachine covering derived initial, NEXT/PREV/GOTO navigation, deriveStepper introspection, recheck() re-seating, boundary-signal proxy, and mockActor gated-step teleport.
Documentation and changeset
packages/ui/src/mosaic/machine/README.md, .claude/skills/mosaic-machine/SKILL.md, .changeset/mosaic-state-machine.md
Full machine README with first-machine walkthrough, building blocks, API table, no-op/recheck semantics, and ConfigureSSO Wizard migration example. Claude SKILL guide with do/don't conventions, setup typing guide, and React consumption patterns. Changeset version marker.

Sequence Diagram(s)

sequenceDiagram
participant UI
participant Hook
participant Runtime
participant Machine
UI->>Hook: mount with machine and options
Hook->>Runtime: createActor
Runtime->>Machine: resolve initial state and context
Hook->>Runtime: start
UI->>Hook: send event
Hook->>Runtime: send
Runtime->>Machine: pick and take transition
Runtime-->>Hook: publish snapshot
Hook-->>UI: render with snapshot
UI->>Hook: unmount
Hook->>Runtime: stop
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Suggested labels

core-3

Suggested reviewers

  • brkalow
  • Ephem

Poem

🐇 Hop, hop! A machine is born today,
States and events to light the way.
assign patches context neat and clean,
always guards flow through the in-between.
useMachine mounts, then stop() on leave —
A rabbit-built runtime you can trust and believe! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 40.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat(ui): add Mosaic state machine — core library, hook, and tests' accurately describes the main change: introduction of a new state machine library with core APIs, React hooks, and comprehensive test coverage for managing multi-step UI flows.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new

pkg-pr-newBot commented Jun 22, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8947

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8947

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8947

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8947

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@8947

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8947

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8947

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8947

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8947

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8947

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8947

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8947

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8947

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8947

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8947

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8947

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8947

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8947

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8947

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8947

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8947

commit: de57772

@github-actions

github-actionsBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-22T19:19:47.984Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on de57772.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
packages/ui/src/mosaic/machine/setup.ts (1)

77-82: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Prefer a typed wrapper over double-casting in fromPromise (Line 81).

fn as unknown as ... hides contract drift. A tiny adapter preserves the same runtime behavior and keeps type checking honest.

💡 Proposed change
- src: fn as unknown as InvokeConfig<TContext, TEvent, TOutput, TStates>['src'],+ src: (context, _event) => fn(context),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/mosaic/machine/setup.ts` around lines 77 - 82, The
double-casting pattern in the src property assignment within fromPromise hides
type contract drift and should be replaced with a typed wrapper function. Create
a wrapper function that takes the context and event parameters (matching
InvokeConfig src signature) but only passes the context parameter to the
original fn, discarding the event parameter. This wrapper preserves the exact
same runtime behavior while maintaining proper type safety without hidden casts.
Assign this wrapper function to src instead of using the double-cast pattern.
packages/ui/src/mosaic/machine/__tests__/machine.test.ts (1)

220-314: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add two invoke regression tests: sync-throw and guard-blocked completion no-op.

The invoke suite is strong, but it should also pin:

  1. invoke.src synchronous throw routes via onError (no actor crash), and
  2. onDone/onError transition blocked by target entry guard is a true no-op (no notify/ref churn).

As per coding guidelines, “Unit tests are required for all new functionality” and “Verify proper error handling and edge cases.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/mosaic/machine/__tests__/machine.test.ts` around lines 220 -
314, Add two new test cases to the existing describe block 'createActor — invoke
(async)'. First, add a test that verifies when invoke.src throws synchronously,
the error is properly routed to onError and the actor does not crash. Second,
add a test that verifies when an onDone or onError transition has a target with
an entry guard that blocks the transition, it results in a true no-op without
any notification or reference management churn. Both tests should follow the
same pattern as the existing tests in the suite, using createMachine,
createActor, and tick utilities to verify the expected behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/ui/src/mosaic/machine/createActor.ts`:
- Around line 145-154: The error handler for invoke.onError is calling commit()
unconditionally after takeTransition(), but takeTransition() can return false
when a transition is blocked by an entry guard. Modify the code to check the
return value of takeTransition() and only call commit() if it returns true. This
same pattern should also be applied to the onDone handler above this code block
to ensure no unnecessary commits occur on blocked transitions.
- Around line 139-156: The current Promise.resolve(invoke.src(context, event as
never)) eagerly evaluates the invoke.src call, which means synchronous errors
thrown from invoke.src escape the promise chain instead of being caught and
routed to the onError handler. To fix this, wrap the invoke.src call in a
deferred microtask by changing Promise.resolve(invoke.src(...)) to
Promise.resolve().then(() => invoke.src(...)) so that both synchronous and
asynchronous errors are consistently caught by the error rejection handler that
calls the onError transition.
In `@packages/ui/src/mosaic/machine/setup.ts`:
- Around line 41-85: The exported setup function at line 41 lacks an explicit
return type annotation, which violates the repository's TypeScript guidelines
for public APIs. Add an explicit return type to the setup function that
describes the object structure it returns, which includes the createMachine,
assign, and fromPromise methods with their respective signatures. The return
type should be defined immediately after the function's closing parenthesis and
before the opening brace of the function body.
---
Nitpick comments:
In `@packages/ui/src/mosaic/machine/__tests__/machine.test.ts`:
- Around line 220-314: Add two new test cases to the existing describe block
'createActor — invoke (async)'. First, add a test that verifies when invoke.src
throws synchronously, the error is properly routed to onError and the actor does
not crash. Second, add a test that verifies when an onDone or onError transition
has a target with an entry guard that blocks the transition, it results in a
true no-op without any notification or reference management churn. Both tests
should follow the same pattern as the existing tests in the suite, using
createMachine, createActor, and tick utilities to verify the expected behavior.
In `@packages/ui/src/mosaic/machine/setup.ts`:
- Around line 77-82: The double-casting pattern in the src property assignment
within fromPromise hides type contract drift and should be replaced with a typed
wrapper function. Create a wrapper function that takes the context and event
parameters (matching InvokeConfig src signature) but only passes the context
parameter to the original fn, discarding the event parameter. This wrapper
preserves the exact same runtime behavior while maintaining proper type safety
without hidden casts. Assign this wrapper function to src instead of using the
double-cast pattern.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: d5481399-00a3-4632-9c85-99189d93cb08

📥 Commits

Reviewing files that changed from the base of the PR and between 7e3174a and dce39c1.

📒 Files selected for processing (14)
  • .changeset/mosaic-state-machine.md
  • .claude/skills/mosaic-machine/SKILL.md
  • packages/ui/src/mosaic/machine/README.md
  • packages/ui/src/mosaic/machine/__tests__/machine.test.ts
  • packages/ui/src/mosaic/machine/__tests__/patterns.test.ts
  • packages/ui/src/mosaic/machine/__tests__/useMachine.test.tsx
  • packages/ui/src/mosaic/machine/__tests__/wizard-migration.test.tsx
  • packages/ui/src/mosaic/machine/assign.ts
  • packages/ui/src/mosaic/machine/createActor.ts
  • packages/ui/src/mosaic/machine/createMachine.ts
  • packages/ui/src/mosaic/machine/setup.ts
  • packages/ui/src/mosaic/machine/types.test-d.ts
  • packages/ui/src/mosaic/machine/types.ts
  • packages/ui/src/mosaic/machine/useMachine.ts

Comment threadpackages/ui/src/mosaic/machine/createActor.ts Outdated
Comment threadpackages/ui/src/mosaic/machine/createActor.ts Outdated
Comment threadpackages/ui/src/mosaic/machine/setup.ts Outdated
- createActor: wrap invoke src in Promise constructor so a synchronous
throw routes to onError rather than escaping as an unhandled exception
(Promise.resolve(fn()) evaluates fn() before the chain begins)
- createActor: guard commit() on takeTransition return value for
onDone/onError handlers, matching the existing after-timer pattern
- setup: add SetupTools interface and explicit return type annotation
Promise.resolve(src()) returns the same native Promise with zero extra
microtask hops; the previous new Promise(r => r(thenable)) added two
hops via the thenable resolution procedure, causing the cooldown-timer
tests to read state before onDone had fired.
Add delete-organization-machine.ts test fixture to the core branch so
machine.test.ts and useMachine.test.tsx compile without a dep on the
sections layer.
@alexcarpenter

Copy link
Copy Markdown
MemberAuthor

closing in favor of #8919

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@alexcarpenter@wobsoriano
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); feat(ui): add Mosaic state machine — core library, hook, and tests by alexcarpenter · Pull Request #8947 · clerk/javascript · GitHub
Skip to content

feat(ui): add Mosaic state machine — core library, hook, and tests - #8947

Closed
alexcarpenter wants to merge 3 commits into
mainfrom
feat/mosaic-machine-core
Closed

feat(ui): add Mosaic state machine — core library, hook, and tests#8947
alexcarpenter wants to merge 3 commits into
mainfrom
feat/mosaic-machine-core

Conversation

@alexcarpenter

@alexcarpenteralexcarpenter commented Jun 22, 2026

Copy link
Copy Markdown
Member

Summary

Introduces packages/ui/src/mosaic/machine: a tiny, dependency-free state machine runtime for modelling multi-step UI flows (confirm dialogs, sign-in wizards, async operations) without XState or any external dependency.

  • createMachine — define states, transitions, guards, and async invocations with full TypeScript inference
  • createActor — runtime that drives the machine; restarts cleanly through React StrictMode double-mount
  • setup() — typed builder with after (delayed transitions) and fromPromise (typed async invoke)
  • assign — immutable context updater helper
  • useMachine / useActor / useSelector — React bindings with stale-closure protection and setContext for syncing external context changes

Tests

  • machine.test.ts — transitions, guards, context, error states
  • useMachine.test.tsx — StrictMode behaviour, setContext, onDone
  • patterns.test.ts — common usage patterns
  • types.test-d.ts — compile-time type assertions
  • wizard-migration.test.tsx — full before/after for migrating a hand-rolled reducer flow

Stack

  1. This PR — core library
  2. feat/mosaic-machine-sections — delete/leave-org section migrations
  3. feat/mosaic-machine-signin — sign-in flow machines + adoption guide

Test plan

  • turbo test --filter @clerk/ui passes on this branch in isolation

Summary by CodeRabbit

  • New Features

    • New Mosaic state machine system for UI state, transitions, guards, async invokes, and actor runtime control
    • Added React hooks integration: useMachine, useActor, useSelector, plus optional transition logging
    • Added helper APIs for building typed machines, including setup, createMachine, assign, and promise-based invokes
    • Added actor utilities, including snapshot “teleporting” via a mock actor
  • Documentation

    • Updated and expanded guides with “first machine” examples and end-to-end migration walkthroughs
    • Added skill guidance for authoring Mosaic-style machines
  • Tests

    • Extensive new test coverage for runtime behavior, hooks, typing, and wizard/pattern parity

Introduces packages/ui/src/mosaic/machine: a tiny, dependency-free
state machine runtime for modelling multi-step flows in the Mosaic UI.
- createMachine / createActor — define states, transitions, and async
invocations (fromPromise) with full TypeScript inference
- setup() — typed builder with after (delayed transitions) and context
helpers
- assign — immutable context updater
- useMachine / useActor / useSelector — React bindings with StrictMode
safety, stale-closure protection, and setContext for syncing external
context changes
- types.test-d.ts + three test suites covering the full API and the
wizard-migration pattern
@changeset-bot

changeset-botBot commented Jun 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: de57772

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Jun 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJun 22, 2026 7:18pm
swingsetReadyReadyPreview, CommentJun 22, 2026 7:18pm

Request Review

@coderabbitai

coderabbitaiBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

An error occurred during the review process. Please try again later.

📝 Walkthrough

Walkthrough

Introduces a new dependency-free, XState v5-style state machine runtime (packages/ui/src/mosaic/machine) including a TypeScript type system, createMachine/assign/setup factories, a full createActor runtime with async invoke/after-timer/recheck support, React hooks (useMachine, useActor, useSelector), comprehensive Vitest tests, and a documentation README plus Claude SKILL guide.

Changes

Mosaic State Machine Library

Layer / File(s)Summary
Type system and contracts
packages/ui/src/mosaic/machine/types.ts
Defines all shared interfaces, types, and constants: event shapes, guard/action primitives, TransitionConfig, invoke/after event types, StateConfig, MachineConfig, StateMachine, Actor interface, Snapshot, and CreateActorOptions.
assign, createMachine, and setup factories
packages/ui/src/mosaic/machine/assign.ts, packages/ui/src/mosaic/machine/createMachine.ts, packages/ui/src/mosaic/machine/setup.ts
Implements assign action creator with isAssignAction guard, createMachine descriptor factory, and setup<TContext, TEvent>() that pre-binds generics for createMachine, assign, and typed fromPromise.
Actor runtime
packages/ui/src/mosaic/machine/createActor.ts
Full actor lifecycle: state entry/exit with action sequencing, async invoke with cancellation tokens, after timer scheduling/cancellation, guarded always transitions, public API (start, stop, send, can, setContext, recheck), snapshot caching/notification, and mockActor for inert teleported initialization.
React hooks
packages/ui/src/mosaic/machine/useMachine.ts
Implements useActor (via useSyncExternalStore), useMachine (actor ownership with lifecycle, live context injection via useLayoutEffect, onDone callback), useSelector (memoized projection with custom equality), and useMachineLogger.
TypeScript type-safety tests
packages/ui/src/mosaic/machine/types.test-d.ts
Verifies compile-time constraints via expectTypeOf and @ts-expect-error: event-union narrowing on send/can, snapshot shape, assign updater signatures, createMachine state-key constraints, setup pre-binding, and fromPromise typed output inference.
Actor runtime unit tests
packages/ui/src/mosaic/machine/__tests__/machine.test.ts
Covers machine introspection, pre-start safety, transitions/guards/actions, always, async invoke, final states, recheck(), observable contract, context initialization precedence, mockActor, pure helpers, StrictMode restart, and after-timer behavior with fake timers.
Real-world UI pattern tests
packages/ui/src/mosaic/machine/__tests__/patterns.test.ts
Verifies eight Clerk UI coordination patterns: multi-entry loading, resend cooldown, error-clear-on-input, conditional initial state, post-invoke routing, typed fromPromise output, on-mount async invoke, and reactive value auto-switch.
React hook and wizard migration tests
packages/ui/src/mosaic/machine/__tests__/useMachine.test.tsx, packages/ui/src/mosaic/machine/__tests__/wizard-migration.test.tsx
Tests useMachine/useActor/useSelector integration (context injection, teleport, re-render scoping, onDone, live context). Builds and tests a createWizardMachine covering derived initial, NEXT/PREV/GOTO navigation, deriveStepper introspection, recheck() re-seating, boundary-signal proxy, and mockActor gated-step teleport.
Documentation and changeset
packages/ui/src/mosaic/machine/README.md, .claude/skills/mosaic-machine/SKILL.md, .changeset/mosaic-state-machine.md
Full machine README with first-machine walkthrough, building blocks, API table, no-op/recheck semantics, and ConfigureSSO Wizard migration example. Claude SKILL guide with do/don't conventions, setup typing guide, and React consumption patterns. Changeset version marker.

Sequence Diagram(s)

sequenceDiagram
participant UI
participant Hook
participant Runtime
participant Machine
UI->>Hook: mount with machine and options
Hook->>Runtime: createActor
Runtime->>Machine: resolve initial state and context
Hook->>Runtime: start
UI->>Hook: send event
Hook->>Runtime: send
Runtime->>Machine: pick and take transition
Runtime-->>Hook: publish snapshot
Hook-->>UI: render with snapshot
UI->>Hook: unmount
Hook->>Runtime: stop
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Suggested labels

core-3

Suggested reviewers

  • brkalow
  • Ephem

Poem

🐇 Hop, hop! A machine is born today,
States and events to light the way.
assign patches context neat and clean,
always guards flow through the in-between.
useMachine mounts, then stop() on leave —
A rabbit-built runtime you can trust and believe! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 40.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat(ui): add Mosaic state machine — core library, hook, and tests' accurately describes the main change: introduction of a new state machine library with core APIs, React hooks, and comprehensive test coverage for managing multi-step UI flows.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new

pkg-pr-newBot commented Jun 22, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8947

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8947

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8947

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8947

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@8947

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8947

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8947

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8947

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8947

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8947

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8947

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8947

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8947

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8947

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8947

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8947

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8947

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8947

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8947

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8947

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8947

commit: de57772

@github-actions

github-actionsBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-22T19:19:47.984Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on de57772.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
packages/ui/src/mosaic/machine/setup.ts (1)

77-82: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Prefer a typed wrapper over double-casting in fromPromise (Line 81).

fn as unknown as ... hides contract drift. A tiny adapter preserves the same runtime behavior and keeps type checking honest.

💡 Proposed change
- src: fn as unknown as InvokeConfig<TContext, TEvent, TOutput, TStates>['src'],+ src: (context, _event) => fn(context),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/mosaic/machine/setup.ts` around lines 77 - 82, The
double-casting pattern in the src property assignment within fromPromise hides
type contract drift and should be replaced with a typed wrapper function. Create
a wrapper function that takes the context and event parameters (matching
InvokeConfig src signature) but only passes the context parameter to the
original fn, discarding the event parameter. This wrapper preserves the exact
same runtime behavior while maintaining proper type safety without hidden casts.
Assign this wrapper function to src instead of using the double-cast pattern.
packages/ui/src/mosaic/machine/__tests__/machine.test.ts (1)

220-314: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add two invoke regression tests: sync-throw and guard-blocked completion no-op.

The invoke suite is strong, but it should also pin:

  1. invoke.src synchronous throw routes via onError (no actor crash), and
  2. onDone/onError transition blocked by target entry guard is a true no-op (no notify/ref churn).

As per coding guidelines, “Unit tests are required for all new functionality” and “Verify proper error handling and edge cases.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/mosaic/machine/__tests__/machine.test.ts` around lines 220 -
314, Add two new test cases to the existing describe block 'createActor — invoke
(async)'. First, add a test that verifies when invoke.src throws synchronously,
the error is properly routed to onError and the actor does not crash. Second,
add a test that verifies when an onDone or onError transition has a target with
an entry guard that blocks the transition, it results in a true no-op without
any notification or reference management churn. Both tests should follow the
same pattern as the existing tests in the suite, using createMachine,
createActor, and tick utilities to verify the expected behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/ui/src/mosaic/machine/createActor.ts`:
- Around line 145-154: The error handler for invoke.onError is calling commit()
unconditionally after takeTransition(), but takeTransition() can return false
when a transition is blocked by an entry guard. Modify the code to check the
return value of takeTransition() and only call commit() if it returns true. This
same pattern should also be applied to the onDone handler above this code block
to ensure no unnecessary commits occur on blocked transitions.
- Around line 139-156: The current Promise.resolve(invoke.src(context, event as
never)) eagerly evaluates the invoke.src call, which means synchronous errors
thrown from invoke.src escape the promise chain instead of being caught and
routed to the onError handler. To fix this, wrap the invoke.src call in a
deferred microtask by changing Promise.resolve(invoke.src(...)) to
Promise.resolve().then(() => invoke.src(...)) so that both synchronous and
asynchronous errors are consistently caught by the error rejection handler that
calls the onError transition.
In `@packages/ui/src/mosaic/machine/setup.ts`:
- Around line 41-85: The exported setup function at line 41 lacks an explicit
return type annotation, which violates the repository's TypeScript guidelines
for public APIs. Add an explicit return type to the setup function that
describes the object structure it returns, which includes the createMachine,
assign, and fromPromise methods with their respective signatures. The return
type should be defined immediately after the function's closing parenthesis and
before the opening brace of the function body.
---
Nitpick comments:
In `@packages/ui/src/mosaic/machine/__tests__/machine.test.ts`:
- Around line 220-314: Add two new test cases to the existing describe block
'createActor — invoke (async)'. First, add a test that verifies when invoke.src
throws synchronously, the error is properly routed to onError and the actor does
not crash. Second, add a test that verifies when an onDone or onError transition
has a target with an entry guard that blocks the transition, it results in a
true no-op without any notification or reference management churn. Both tests
should follow the same pattern as the existing tests in the suite, using
createMachine, createActor, and tick utilities to verify the expected behavior.
In `@packages/ui/src/mosaic/machine/setup.ts`:
- Around line 77-82: The double-casting pattern in the src property assignment
within fromPromise hides type contract drift and should be replaced with a typed
wrapper function. Create a wrapper function that takes the context and event
parameters (matching InvokeConfig src signature) but only passes the context
parameter to the original fn, discarding the event parameter. This wrapper
preserves the exact same runtime behavior while maintaining proper type safety
without hidden casts. Assign this wrapper function to src instead of using the
double-cast pattern.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: d5481399-00a3-4632-9c85-99189d93cb08

📥 Commits

Reviewing files that changed from the base of the PR and between 7e3174a and dce39c1.

📒 Files selected for processing (14)
  • .changeset/mosaic-state-machine.md
  • .claude/skills/mosaic-machine/SKILL.md
  • packages/ui/src/mosaic/machine/README.md
  • packages/ui/src/mosaic/machine/__tests__/machine.test.ts
  • packages/ui/src/mosaic/machine/__tests__/patterns.test.ts
  • packages/ui/src/mosaic/machine/__tests__/useMachine.test.tsx
  • packages/ui/src/mosaic/machine/__tests__/wizard-migration.test.tsx
  • packages/ui/src/mosaic/machine/assign.ts
  • packages/ui/src/mosaic/machine/createActor.ts
  • packages/ui/src/mosaic/machine/createMachine.ts
  • packages/ui/src/mosaic/machine/setup.ts
  • packages/ui/src/mosaic/machine/types.test-d.ts
  • packages/ui/src/mosaic/machine/types.ts
  • packages/ui/src/mosaic/machine/useMachine.ts

Comment threadpackages/ui/src/mosaic/machine/createActor.ts Outdated
Comment threadpackages/ui/src/mosaic/machine/createActor.ts Outdated
Comment threadpackages/ui/src/mosaic/machine/setup.ts Outdated
- createActor: wrap invoke src in Promise constructor so a synchronous
throw routes to onError rather than escaping as an unhandled exception
(Promise.resolve(fn()) evaluates fn() before the chain begins)
- createActor: guard commit() on takeTransition return value for
onDone/onError handlers, matching the existing after-timer pattern
- setup: add SetupTools interface and explicit return type annotation
Promise.resolve(src()) returns the same native Promise with zero extra
microtask hops; the previous new Promise(r => r(thenable)) added two
hops via the thenable resolution procedure, causing the cooldown-timer
tests to read state before onDone had fired.
Add delete-organization-machine.ts test fixture to the core branch so
machine.test.ts and useMachine.test.tsx compile without a dep on the
sections layer.
@alexcarpenter

Copy link
Copy Markdown
MemberAuthor

closing in favor of #8919

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@alexcarpenter@wobsoriano