feat(ui): add Mosaic state machine — core library, hook, and tests#8947
feat(ui): add Mosaic state machine — core library, hook, and tests#8947alexcarpenter wants to merge 3 commits into
Conversation
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 detectedLatest commit: de57772 The changes in this PR will be included in the next version bump. This PR includes changesets to release 0 packagesWhen 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 |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedAn error occurred during the review process. Please try again later. 📝 WalkthroughWalkthroughIntroduces a new dependency-free, XState v5-style state machine runtime ( ChangesMosaic State Machine Library
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
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ 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. Comment |
@clerk/astro
@clerk/backend
@clerk/chrome-extension
@clerk/clerk-js
@clerk/eslint-plugin
@clerk/expo
@clerk/expo-passkeys
@clerk/express
@clerk/fastify
@clerk/hono
@clerk/localizations
@clerk/nextjs
@clerk/nuxt
@clerk/react
@clerk/react-router
@clerk/shared
@clerk/tanstack-react-start
@clerk/testing
@clerk/ui
@clerk/upgrade
@clerk/vue
commit: |
API Changes Report
Summary
No API Changes DetectedAll packages have stable APIs with no detected changes. Report generated by Break Check Last ran on |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
packages/ui/src/mosaic/machine/setup.ts (1)
77-82: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winPrefer 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 winAdd two invoke regression tests: sync-throw and guard-blocked completion no-op.
The invoke suite is strong, but it should also pin:
invoke.srcsynchronous throw routes viaonError(no actor crash), andonDone/onErrortransition 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
📒 Files selected for processing (14)
.changeset/mosaic-state-machine.md.claude/skills/mosaic-machine/SKILL.mdpackages/ui/src/mosaic/machine/README.mdpackages/ui/src/mosaic/machine/__tests__/machine.test.tspackages/ui/src/mosaic/machine/__tests__/patterns.test.tspackages/ui/src/mosaic/machine/__tests__/useMachine.test.tsxpackages/ui/src/mosaic/machine/__tests__/wizard-migration.test.tsxpackages/ui/src/mosaic/machine/assign.tspackages/ui/src/mosaic/machine/createActor.tspackages/ui/src/mosaic/machine/createMachine.tspackages/ui/src/mosaic/machine/setup.tspackages/ui/src/mosaic/machine/types.test-d.tspackages/ui/src/mosaic/machine/types.tspackages/ui/src/mosaic/machine/useMachine.ts
- 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.
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 inferencecreateActor— runtime that drives the machine; restarts cleanly through React StrictMode double-mountsetup()— typed builder withafter(delayed transitions) andfromPromise(typed async invoke)assign— immutable context updater helperuseMachine/useActor/useSelector— React bindings with stale-closure protection andsetContextfor syncing external context changesTests
machine.test.ts— transitions, guards, context, error statesuseMachine.test.tsx— StrictMode behaviour,setContext,onDonepatterns.test.ts— common usage patternstypes.test-d.ts— compile-time type assertionswizard-migration.test.tsx— full before/after for migrating a hand-rolled reducer flowStack
feat/mosaic-machine-sections— delete/leave-org section migrationsfeat/mosaic-machine-signin— sign-in flow machines + adoption guideTest plan
turbo test --filter @clerk/uipasses on this branch in isolationSummary by CodeRabbit
New Features
useMachine,useActor,useSelector, plus optional transition loggingsetup,createMachine,assign, and promise-based invokesDocumentation
Tests