Skip to content

refactor(ui): drive ConfigureSSO wizard navigation with a state machine#8715

Merged
iagodahlem merged 35 commits into
mainfrom
iago/orgs-1599-configure-sso-state-machine
Jun 9, 2026
Merged

refactor(ui): drive ConfigureSSO wizard navigation with a state machine#8715
iagodahlem merged 35 commits into
mainfrom
iago/orgs-1599-configure-sso-state-machine

Conversation

@iagodahlem

@iagodahlem iagodahlem commented Jun 1, 2026

Copy link
Copy Markdown
Member

What

Refactors <ConfigureSSO /> wizard navigation onto a guard-based state machine (ORGS-1599, from Alex Carpenter's named-guards + reducer-based-navigation feedback). The public component API is unchanged — this is an internal architecture change so navigation derives from connection state instead of imperative per-step routing.

Why

The previous wizard had four structural problems that would calcify as more providers and DNS/TXT verification land:

  1. Steps routed on data — navigation decisions lived inside step bodies, not the wizard.
  2. Data was scattered and duplicated — connection / email / test-run reads were repeated across steps.
  3. Mutations threaded through props with inconsistent reverification — some sensitive writes were unwrapped.
  4. Footer / submit lifecycle was copy-pasted per step, patched over with effect sentinels.

The model

Each step carries one inline guard: () => boolean — an entry precondition ("may navigation land here now?"). Everything derives from it, uniformly:

  • Initial step (and reload): the furthest contiguously-reachable step, derived from the connection's state — so a reload lands where the user was.
  • Next / Previous: sequential; advance or retreat only when the target step's guard holds, otherwise no-op.
  • Stepper: jumps to a step only when its guard holds — the same predicate gates the click and the disabled state, so they can't diverge.
  • Reset: deletes the connection; the wizard self-corrects to the furthest-reachable step when the current step's guard breaks — no imperative navigation, no remount. This also heals an external/mid-flow delete.

The reducer is pure and unit-tested without React.

What's in here

  • A single umbrella data hook — connection + domain-derived state + reverification-wrapped mutations + two-tier test-run loading (cold skeleton vs. background table refetch) — gated above the provider so the context never observes loading.
  • The generic wizard primitive (pure reducer + machine + <Wizard.Match>), with the machine hidden behind useWizard(); nested SAML / verify sub-flows preserved.
  • All step bodies migrated to the new navigation.

Scope / follow-ups

  • The verify-domain step is unchanged (still email-OTP); the DNS/TXT rewrite and the domains[] create-param work land as a follow-up on top of this.
  • @clerk/ui patch changeset (no public API change).

Tests

Unit tests cover the reducer (sequential / positional navigation, reachability, self-correction, the nested-flow boundary), the domain derivation, and the data hooks. The flow was manually QA'd end-to-end: initial-state derivation, create → configure, breadcrumb gating, positional back, reload, and reset (including from inside the nested configure sub-flow).

Summary by CodeRabbit

  • New Features

    • Improved SSO configuration wizard with smarter step progression based on connection state and configuration completeness.
    • Enhanced enterprise connection test-run management with better pagination and polling controls.
  • Bug Fixes

    • Fixed step navigation to maintain correct position after page reload.
    • Improved connection reset flow to properly navigate to the appropriate configuration step.
  • Documentation

    • Updated wizard component documentation to reflect new guard-based navigation behavior.

@vercel

vercel Bot commented Jun 1, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Jun 9, 2026 7:52pm

Request Review

@changeset-bot

changeset-bot Bot commented Jun 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 251930c

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

This PR includes changesets to release 20 packages
Name Type
@clerk/ui Patch
@clerk/shared Patch
@clerk/clerk-js Patch
@clerk/astro Patch
@clerk/chrome-extension Patch
@clerk/react Patch
@clerk/vue Patch
@clerk/backend Patch
@clerk/expo-passkeys Patch
@clerk/expo Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/hono Patch
@clerk/localizations Patch
@clerk/msw Patch
@clerk/nextjs Patch
@clerk/nuxt Patch
@clerk/react-router Patch
@clerk/tanstack-react-start Patch
@clerk/testing Patch

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

@pkg-pr-new

pkg-pr-new Bot commented Jun 1, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 251930c

Comment thread packages/ui/src/components/ConfigureSSO/ConfigureSSOSteps.tsx Outdated
Comment thread packages/ui/src/components/ConfigureSSO/ConfigureSSOSteps.tsx Outdated
Comment thread packages/ui/src/components/ConfigureSSO/domain/enterpriseConnection.ts Outdated
Comment thread packages/ui/src/components/ConfigureSSO/domain/enterpriseConnection.ts Outdated
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR refactors ConfigureSSO's wizard navigation from a JSX-child registration model to a declarative guard-based state machine. It centralizes enterprise-connection state management into upstream hooks, reshapes the provider context, updates step flows to use new navigation and mutation APIs, and adds comprehensive test coverage for the new architecture.

Changes

ConfigureSSO Guard-Based Wizard Architecture

Layer / File(s) Summary
Release notes and shared test-runs hook
.changeset/*, packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx, packages/shared/src/types/enterpriseConnection.ts
Patch version bump with wizard refactor notes; extends test-runs hook with keepPreviousData to preserve prior rows during refetch, and adds optional RevalidateTestRunsOptions with armPolling control for conditional polling arming.
Wizard public contracts and reducer
packages/ui/src/components/ConfigureSSO/elements/Wizard/types.ts, packages/ui/src/components/ConfigureSSO/elements/Wizard/reducer.ts
Replaces JSX-children step registration with WizardStepConfig (id, label, guard, hidden); WizardState tracks current step, direction, and hasNavigated; reducer implements guarded initialization and NEXT/PREV/GOTO transitions with no-op identity preservation.
Wizard engine and useWizardMachine
packages/ui/src/components/ConfigureSSO/elements/Wizard/Wizard.tsx, packages/ui/src/components/ConfigureSSO/elements/Wizard/useWizardMachine.ts, packages/ui/src/components/ConfigureSSO/elements/Wizard/WizardContext.tsx, packages/ui/src/components/ConfigureSSO/elements/Wizard/index.ts
Rewrites Wizard to accept steps config array and delegate to useWizardMachine; adds Wizard.Match for render-only step body selection; implements useWizardMachine with guard evaluation, reseating for invalid steps, SEAM bubbling, and breadcrumb derivation.
Wizard engine tests
packages/ui/src/components/ConfigureSSO/elements/Wizard/__tests__/*
Adds test suites for Wizard.Match rendering, reducer guard/transition behavior, nested wizard bubbling, reseating/clamping when guards change, and onStepChange callback correctness.
Domain model and factory
packages/ui/src/components/ConfigureSSO/domain/organizationEnterpriseConnection.ts, packages/ui/src/components/ConfigureSSO/domain/__tests__/*
Introduces immutable snapshot with derived fields (provider, active, minimum config, verified email, successful test run); exports predicate and pure factory; includes tests for derived behavior and purity.
Test-runs query hook
packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts
Centralizes success probe and paginated list queries, exposes distinct isLoading (cold) and isFetching (refetch) signals, supports pagination and refresh(options?) with optional polling arming.
Organization connection umbrella hook
packages/ui/src/components/ConfigureSSO/hooks/useOrganizationEnterpriseConnection.ts, packages/ui/src/components/ConfigureSSO/hooks/__tests__/*
Aggregates enterprise-connection query, test-runs state, user/org data, derived domain model, and mutation APIs; gates global isLoading on initial-connection presence; includes gating and mutation forwarding tests.
ConfigureSSO context and step graph
packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx, packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx, packages/ui/src/components/ConfigureSSO/ConfigureSSOSteps.tsx, packages/ui/src/components/ConfigureSSO/ConfigureSSOHeader.tsx
Refactors provider context to accept upstream aggregated data; introduces ConfigureSSOSteps with guard predicates controlling step reachability (e.g., verify-domain always reachable, others gated on connection state); updates header to use wizard's activeSteps and goToStep.
Reset and confirmation flows
packages/ui/src/components/ConfigureSSO/ResetConnectionDialog.tsx, packages/ui/src/components/ConfigureSSO/steps/ConfirmationStep.tsx, packages/ui/src/components/ConfigureSSO/elements/Step.tsx, packages/ui/src/components/ConfigureSSO/__tests__/*
Migrates reset and active-toggle to context mutations.deleteConnection and mutations.setConnectionActive; updates reset button visibility based on organizationEnterpriseConnection.hasConnection; removes wizard navigation from reset flow.
Verify-domain and provider-selection
packages/ui/src/components/ConfigureSSO/steps/VerifyDomainStep.tsx, packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx, packages/ui/src/components/ConfigureSSO/steps/__tests__/*
Refactors verify-domain inner flow to Wizard.Match bodies with VerifyDomainRefsContext; rewires select-provider to derive provider/email from context, call createConnection, and advance via goNext.
Configure and SAML sub-wizards
packages/ui/src/components/ConfigureSSO/steps/ConfigureStep/*, packages/ui/src/components/ConfigureSSO/steps/ConfigureStep/saml/*
Updates ConfigureStep to read provider from domain model; refactors SAML custom/google/microsoft/okta flows from Wizard.Step to WizardStepConfig with Wizard.Match routing; moves step headers into step components; migrates mutation calls to mutations.updateConnection.
Test configuration step
packages/ui/src/components/ConfigureSSO/steps/TestConfigurationStep.tsx, packages/ui/src/components/ConfigureSSO/steps/__tests__/*
Migrates to context-provided testRuns state, pagination, and refresh(); adds mount-only refresh when not on initial step; updates test creation/continue to use context mutations and goNext.
clerk-js domains parameter
packages/clerk-js/src/utils/enterpriseConnection.ts, packages/clerk-js/src/core/resources/__tests__/Organization.test.ts
Adds optional domains?: string[] to create params and serializes non-empty array into request body; includes tests for presence and absence.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

clerk-js

Suggested reviewers

  • alexisintech

🐰 A wizard machine guides the steps with grace,
Guard predicates keepeth each one in place,
No remount when guards flip, no needless refetch,
The reset doth follow—our SSO is set!

iagodahlem added 11 commits June 8, 2026 15:51
Compose the org-scoped connection source query, the domain aggregate,
reverification-wrapped mutations, and the test-run state into a single
umbrella hook with one isLoading gate.

Forward a keepPreviousData option through the shared
useOrganizationEnterpriseConnectionTestRuns hook (additive,
backwards-compatible) so paginating an existing-at-load test-run list keeps
previous rows visible and surfaces as isFetching instead of tearing down to a
cold-load skeleton. The success probe stays on its own cache key without it.

Decouple polling from an empty list on entry: revalidate now only arms polling
when the caller opts in, so landing on the test step with a freshly-configured
zero-run connection no longer starts polling on its own.
Replace the direct enterprise-connection plumbing with a single umbrella
data hook call, gating the skeleton above the provider so the context
never observes loading. Reshape the wizard context to expose the domain
aggregate, mutations, test-runs, and primary email the steps need, and
derive the step graph from inline entry guards in a dedicated steps
component. Drive the breadcrumb stepper off the guard-driven reachability
flag and relocate the reset control to a single top-level slot. Remove
the now-superseded initial-step derivation helper.
Move every step body to the umbrella data context and the entry-guard
wizard facade: select-provider creates the connection then advances with
a plain goNext (and never re-creates on revisit), the test step consumes
the shared test-runs view and arms polling after a run, and each SAML
sub-flow hosts its own linear nested wizard pinned to its first step.
Update the step tests to assert the new guard-gated navigation.
Drop the per-step reset footer control and its subcomponent so the reset
affordance is rendered once, at the wizard's top level, where its
navigation resolves against the outer wizard instead of a nested
sub-flow. Point the reset dialog at the bundled delete mutation and jump
back to the entry step once the connection is gone.
@LauraBeatris

Copy link
Copy Markdown
Member
CleanShot.2026-06-08.at.17.21.13.mp4

goNext is not moving to the next step after creating the connection, I believe it might be something related to the guards conditions - perhaps with that useRef

@LauraBeatris

Copy link
Copy Markdown
Member
CleanShot.2026-06-08.at.17.23.46.mp4

When successfully adding the IdP metadata, it doesn't navigate to the next step, it requires an extra click on "Continue"

@LauraBeatris

LauraBeatris commented Jun 8, 2026

Copy link
Copy Markdown
Member

I noticed we aren't pooling for successful test runs anymore but relying on "Refresh logs" only - was this an expected change as part of this PR?

Also on mount we're performing 4 requests

CleanShot.2026-06-08.at.17.42.18.mp4

…cation

The org-scoped enterprise-connection FAPI endpoints carry no reverification
middleware, so the mutations never receive session_reverification_required and
the useReverification wrapping was dead code. Define the mutations directly on
the umbrella useOrganizationEnterpriseConnection hook, calling the underlying
handles directly, and drop the separate mutations hook. The mutations surface
on the context is unchanged.
The org-scoped enterprise-connection create endpoint requires domains, but the
SDK only sent provider and name, producing a 422 form_param_missing error. Add
an optional domains array to the create params and serialize each entry as a
repeated domains form field. The domains are derived from the admin's email
domain when creating a connection. Omitted or empty domains are not sent, so
existing callers are unaffected.
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-09T19:58:52.016Z

Summary

Metric Count
Packages analyzed 19
Packages with changes 2
🔴 Breaking changes 6
🟡 Non-breaking changes 0
🟢 Additions 0

Warning
6 breaking change(s) detected - Major version bump required

🤖 This report was reviewed by claude-sonnet-4-6.

Note
Break Check could not snapshot 3 subpaths; the diff below excludes them.

  • @clerk/astro ./env: Internal Error: Unable to determine module for: /home/runner/_work/javascript/javascript/packages/astro/env.d.ts You have encountered a software defect. Please consider reporting the issue to the maintainers of this application.
  • @clerk/shared ./cookie: Internal Error: Unable to follow symbol for "Cookies" You have encountered a software defect. Please consider reporting the issue to the maintainers of this application.
  • @clerk/testing ./cypress: Symbol not found for identifier: Cypress

🔴 Breaking changes index (6)

Every breaking change, up front. Full diffs are in the package sections below.

Package Subpath Change
@clerk/astro ./client $authStore
@clerk/astro ./client $organizationStore
@clerk/astro ./client $userStore
@clerk/shared ./react UseOrganizationEnterpriseConnectionTestRunsParams
@clerk/shared ./react UseOrganizationEnterpriseConnectionTestRunsReturn
@clerk/shared ./types CreateOrganizationEnterpriseConnectionParams

@clerk/astro

Current version: 3.3.3
Recommended bump: MAJOR → 4.0.0

Subpath ./client

🔴 Breaking Changes (3)

Changed: $authStore
  $authStore: nanostores.ReadableAtom<{
      userId: string | null | undefined;
-     user: _clerk_shared__chunks_index_BTxz__kZ.hs | null | undefined;
+     user: _clerk_shared__chunks_index_U8nAlSHe.hs | null | undefined;
      sessionId: string | null | undefined;
-     session: _clerk_shared__chunks_index_BTxz__kZ.es | null | undefined;
+     session: _clerk_shared__chunks_index_U8nAlSHe.es | null | undefined;
      sessionStatus: "active" | "pending" | undefined;
-     sessionClaims: _clerk_shared__chunks_index_BTxz__kZ.ro | null | undefined;
-     organization: _clerk_shared__chunks_index_BTxz__kZ.Co | null | undefined;
+     sessionClaims: _clerk_shared__chunks_index_U8nAlSHe.ro | null | undefined;
+     organization: _clerk_shared__chunks_index_U8nAlSHe.Co | null | undefined;
      orgId: string | null | undefined;
      orgRole: string | null | undefined;
      orgSlug: string | null | undefined;
-     orgPermissions: _clerk_shared__chunks_index_BTxz__kZ.ym<_clerk_shared__chunks_index_BTxz__kZ.uo>[] | null | undefined;
-     actor: _clerk_shared__chunks_index_BTxz__kZ.Qa | null | undefined;
+     orgPermissions: _clerk_shared__chunks_index_U8nAlSHe.ym<_clerk_shared__chunks_index_U8nAlSHe.uo>[] | null | undefined;
+     actor: _clerk_shared__chunks_index_U8nAlSHe.Qa | null | undefined;
      factorVerificationAge: [number, number] | null;
  }>

Static analyzer: Breaking change in variable $authStore: Type changed: $authStore:import("nanostores").ReadableAtom<{userId:string|null|undefined;user:!_clerk_shared__chunks_index_BTxz__kZ.h…$authStore:import("nanostores").ReadableAtom<{userId:string|null|undefined;user:!_clerk_shared__chunks_index_U8nAlSHe.h…

🤖 AI review (confirmed) (95%): The type references changed from _clerk_shared__chunks_index_BTxz__kZ to _clerk_shared__chunks_index_U8nAlSHe, both of which are content-hashed internal chunk paths (containing /_chunks/ segments with hash suffixes) that are not resolvable public entry points; per rule 12, structural equivalence does not save this.

Migration: Upgrade all @clerk/shared and @clerk/astro dependencies together to a consistent version so the internal chunk references align.

Changed: $organizationStore
- $organizationStore: nanostores.ReadableAtom<_clerk_shared__chunks_index_BTxz__kZ.Co | null | undefined>
+ $organizationStore: nanostores.ReadableAtom<_clerk_shared__chunks_index_U8nAlSHe.Co | null | undefined>

Static analyzer: Breaking change in variable $organizationStore: Type changed: $organizationStore:import("nanostores").ReadableAtom<!_clerk_shared__chunks_index_BTxz__kZ.Co:type|null|undefined>$organizationStore:import("nanostores").ReadableAtom<!_clerk_shared__chunks_index_U8nAlSHe.Co:type|null|undefined>

🤖 AI review (confirmed) (95%): The Co type referenced in $organizationStore moved from the non-resolvable internal chunk _clerk_shared__chunks_index_BTxz__kZ to _clerk_shared__chunks_index_U8nAlSHe; both are content-hashed bundler chunks inaccessible as public entry points (rule 12).

Migration: Upgrade all @clerk/shared and @clerk/astro dependencies together to a consistent version so the internal chunk references align.

Changed: $userStore
- $userStore: nanostores.ReadableAtom<_clerk_shared__chunks_index_BTxz__kZ.hs | null | undefined>
+ $userStore: nanostores.ReadableAtom<_clerk_shared__chunks_index_U8nAlSHe.hs | null | undefined>

Static analyzer: Breaking change in variable $userStore: Type changed: $userStore:import("nanostores").ReadableAtom<!_clerk_shared__chunks_index_BTxz__kZ.hs:type|null|undefined>$userStore:import("nanostores").ReadableAtom<!_clerk_shared__chunks_index_U8nAlSHe.hs:type|null|undefined>

🤖 AI review (confirmed) (95%): The hs type referenced in $userStore moved from the non-resolvable internal chunk _clerk_shared__chunks_index_BTxz__kZ to _clerk_shared__chunks_index_U8nAlSHe; both are content-hashed bundler chunks inaccessible as public entry points (rule 12).

Migration: Upgrade all @clerk/shared and @clerk/astro dependencies together to a consistent version so the internal chunk references align.


@clerk/shared

Current version: 4.15.0
Recommended bump: MAJOR → 5.0.0

Subpath ./react

🔴 Breaking Changes (2)

Changed: UseOrganizationEnterpriseConnectionTestRunsParams
// ... 2 unchanged lines elided ...
    params?: GetEnterpriseConnectionTestRunsParams;
    pollIntervalMs?: number;
    enabled?: boolean;
+   keepPreviousData?: boolean;
  };

Static analyzer: Breaking change in type alias UseOrganizationEnterpriseConnectionTestRunsParams: Type changed: {enterpriseConnectionId:string|null;params?:import("@clerk/shared").~GetEnterpriseConnectionTestRunsParams;pollInterval…{enterpriseConnectionId:string|null;params?:import("@clerk/shared").~GetEnterpriseConnectionTestRunsParams;pollInterval…

🤖 AI review (suggests non-breaking, not applied) (95%): A new optional property keepPreviousData?: boolean was added to an input/params type; existing consumers who construct this object are unaffected since the property is optional and they are not required to provide it.

Kept breaking by the reviewer; re-run with --ai-apply-downgrades to apply this relaxation.

Changed: UseOrganizationEnterpriseConnectionTestRunsReturn
// ... 4 unchanged lines elided ...
    isLoading: boolean;
    isFetching: boolean;
    isPolling: boolean;
-   revalidate: () => Promise<void>;
+   revalidate: (options?: RevalidateTestRunsOptions) => Promise<void>;
  };

Static analyzer: Breaking change in type alias UseOrganizationEnterpriseConnectionTestRunsReturn: Type changed: {data:import("@clerk/shared").~EnterpriseConnectionTestRunResource[]|undefined;totalCount:number|undefined;error:!Error…{data:import("@clerk/shared").~EnterpriseConnectionTestRunResource[]|undefined;totalCount:number|undefined;error:!Error…

🤖 AI review (confirmed) (85%): The revalidate function on the return type changed from () => Promise<void> to (options?: RevalidateTestRunsOptions) => Promise<void>; while the parameter is optional, consumers who assigned or typed revalidate as () => Promise<void> (e.g. stored it in a variable with that signature) will see a type mismatch because the new function type is not assignable to the old one in all structural contexts.

Migration: Update any code that stores or types revalidate as () => Promise<void> to accept the new optional parameter signature (options?: RevalidateTestRunsOptions) => Promise<void>.

Subpath ./types

🔴 Breaking Changes (1)

Changed: CreateOrganizationEnterpriseConnectionParams
  type CreateOrganizationEnterpriseConnectionParams = {
    provider: OrganizationEnterpriseConnectionProvider;
    name: string;
+   domains?: string[];
    organizationId?: string | null;
    saml?: OrganizationEnterpriseConnectionSamlInput | null;
    oidc?: OrganizationEnterpriseConnectionOidcInput | null;
// ... 1 unchanged line elided ...

Static analyzer: Breaking change in type alias CreateOrganizationEnterpriseConnectionParams: Type changed: {provider:import("@clerk/shared").OrganizationEnterpriseConnectionProvider;name:string;organizationId?:string|null;saml…{provider:import("@clerk/shared").OrganizationEnterpriseConnectionProvider;name:string;domains?:string[];organizationId…

🤖 AI review (suggests non-breaking, not applied) (95%): The only change is the addition of a new optional property domains?: string[] to CreateOrganizationEnterpriseConnectionParams, which is used as an input parameter type. Adding an optional property to an input type does not break any existing well-typed consumer code that constructs or passes values of this type.

Kept breaking by the reviewer; re-run with --ai-apply-downgrades to apply this relaxation.


Report generated by Break Check

Last ran on 251930c.

@coderabbitai coderabbitai Bot 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: 1

🤖 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/shared/src/types/enterpriseConnection.ts`:
- Around line 144-145: The EnterpriseConnection type currently declares
domains?: string[] but the org-scoped create endpoint requires domains; update
the type contract so callers can't compile without domains: either (A) make
domains required by changing domains?: string[] to domains: string[] on the
EnterpriseConnection used by the org-scoped create path, or (B) introduce two
distinct types (e.g., OrgScopedEnterpriseConnection with domains: string[] and
LegacyEnterpriseConnection with domains?: string[]) and update the org-scoped
create API signatures to accept OrgScopedEnterpriseConnection instead; ensure
all references to EnterpriseConnection in create handlers or factories are
updated to use the correct new type.
🪄 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: 1d3c694d-cfb4-40fc-8f29-35bf14c5a49a

📥 Commits

Reviewing files that changed from the base of the PR and between 2cb9278 and 227637b.

📒 Files selected for processing (9)
  • .changeset/quick-mammals-flash.md
  • packages/clerk-js/src/core/resources/__tests__/Organization.test.ts
  • packages/clerk-js/src/utils/enterpriseConnection.ts
  • packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx
  • packages/shared/src/types/enterpriseConnection.ts
  • packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx
  • packages/ui/src/components/ConfigureSSO/domain/organizationEnterpriseConnection.ts
  • packages/ui/src/components/ConfigureSSO/hooks/__tests__/useOrganizationEnterpriseConnection.test.tsx
  • packages/ui/src/components/ConfigureSSO/hooks/useOrganizationEnterpriseConnection.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/quick-mammals-flash.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx
  • packages/ui/src/components/ConfigureSSO/hooks/useOrganizationEnterpriseConnection.ts
  • packages/ui/src/components/ConfigureSSO/domain/organizationEnterpriseConnection.ts

Comment on lines +144 to +145
/** FQDN strings the connection authenticates. Required by the org-scoped create endpoint. */
domains?: string[];

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.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

domains contract is contradictory (required in docs, optional in type).

Line 144 says org-scoped create requires domains, but Line 145 keeps domains optional. That leaves a compile-time path that can still hit the 422 this PR is addressing.

Suggested direction
export type CreateOrganizationEnterpriseConnectionParams = {
  provider: OrganizationEnterpriseConnectionProvider;
  name: string;
- /** FQDN strings the connection authenticates. Required by the org-scoped create endpoint. */
- domains?: string[];
+ /** FQDN strings the connection authenticates. Required for org-scoped create calls. */
+ domains: [string, ...string[]];
  organizationId?: string | null;
  saml?: OrganizationEnterpriseConnectionSamlInput | null;
  oidc?: OrganizationEnterpriseConnectionOidcInput | null;
};

If backward compatibility prevents this direct change, consider splitting org-scoped and legacy/me-scoped param types instead of documenting a required field as optional.

🤖 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/shared/src/types/enterpriseConnection.ts` around lines 144 - 145,
The EnterpriseConnection type currently declares domains?: string[] but the
org-scoped create endpoint requires domains; update the type contract so callers
can't compile without domains: either (A) make domains required by changing
domains?: string[] to domains: string[] on the EnterpriseConnection used by the
org-scoped create path, or (B) introduce two distinct types (e.g.,
OrgScopedEnterpriseConnection with domains: string[] and
LegacyEnterpriseConnection with domains?: string[]) and update the org-scoped
create API signatures to accept OrgScopedEnterpriseConnection instead; ensure
all references to EnterpriseConnection in create handlers or factories are
updated to use the correct new type.

A submit-then-advance step resolves its create/update with the cache already
revalidated, but React has not re-rendered yet, so goNext read a stale config
and the next step's guard still failed — the advance no-op'd and the user had
to click Continue a second time.

goNext now defers a guard-blocked mid-flow advance instead of hard-stopping:
it parks the from-position and a render-phase resolver re-reduces NEXT against
the fresh config on the next render, advancing as soon as the guard holds. Any
explicit navigation (goPrev/goToStep) abandons a pending advance, and the
reachability clamp takes precedence when the active step becomes unreachable.
The terminal-position parent bubble is unchanged, so a nested sub-flow's last
step still advances the parent (which defers in turn if needed).
The provider-select and SAML identity-provider-metadata steps submit, then
advance. Because the advance is now deferred to a later render, the shared card
loading would drop to idle for a frame before the step unmounted, flashing the
next step in a loading state.

Move the button's loading to a step-local submit flag and do not reset it on
success: the deferred advance unmounts the step, which ends the loading
visually with no idle frame, and the next step mounts with its own fresh state.
The flag is reset only on error, where the step stays mounted.
…hing on step re-entry

Arm the shared test-runs hook's empty-to-first-row poll when a user kicks off
a test run, so the new run surfaces on its own instead of requiring a manual
"Refresh logs" click. The manual refresh button stays a one-shot, no-arm
refresh so it never starts a perpetual poll.

Also drop the mount effect that re-invalidated both the probe and the list on
navigating into the test step. The queries live in the umbrella hook above the
step (they do not unmount on leaving), and once a kicked-off run arms its own
poll the data stays fresh, so re-entry has nothing new to fetch. Net: two
requests on entry instead of four, and one fewer effect.
Simplify the JSDoc on the stepper bullet-number field, and add init-state
coverage asserting the wizard mounts on the test step for a configured-but-
inactive connection with no successful test run. Mark the verified-email
select-provider case as a placeholder for an upcoming TXT domain-verification
test.
Add integration-level coverage for the self-serve SSO wizard's navigation
contract where the unit tests don't reach:

- init matrix: mount on verify-domain for an unverified email, and on
  confirmation for a configured-but-inactive connection with a successful
  test run (distinct from the active short-circuit)
- create -> deferred advance: select-provider Continue resolves to configure
  once the created connection lands through the real revalidate path
- reset -> re-derive: deleting from the test step self-corrects to the
  furthest-reachable step (no strand)
- card errors do not leak across steps via onStepChange
Wrap each top-level ConfigureSSO step in its own card-state provider so a
card error raised on one step lives in that step's scope only. Leaving a
step unmounts its provider and clears the error, including on an emergent
clamp/reset re-seat — the case the prior shared-card step-change callback
missed because the clamp re-seats through raw setState. Drops the now-unused
step-change plumbing from the wizard.
The goNext docs still described the pre-deferral hard-stop model. Rewrite the
three sites to the real contract: a guard-blocked mid-flow goNext parks a
pending advance that resolves once the next guard is satisfied while still on
the step, abandoned by goPrev/goToStep, bubbling only from terminal. Add the
call-site rule (only call goNext after the action that satisfies the next
guard, or pre-check first). goPrev's hard-stop wording is unchanged.
The "primary, else first unverified address" rule was written three times and
two copies had drifted. Extract one pure selector in the domain module and
have the umbrella hook and the verify-domain step consume it, so the guards
and the UI can no longer disagree. The create path keeps deriving the domain
from the verified primary only. Adds a focused unit test for the selector.
No step ever set `hidden`; breadcrumb membership is already decided by the
header filtering on `label`. Remove the dead flag from the step config and the
activeSteps filter (activeSteps is now every descriptor in order) along with
the stale docs and the hidden-specific test. First/last bounding and the
breadcrumb are unchanged.
@iagodahlem iagodahlem merged commit 032632c into main Jun 9, 2026
46 checks passed
@iagodahlem iagodahlem deleted the iago/orgs-1599-configure-sso-state-machine branch June 9, 2026 20:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants