Skip to content

fix(ui): revalidate test runs before advancing from the Test step#9046

Open
iagodahlem wants to merge 5 commits into
mainfrom
iago/orgs-1701-test-runs-on-continue
Open

fix(ui): revalidate test runs before advancing from the Test step#9046
iagodahlem wants to merge 5 commits into
mainfrom
iago/orgs-1701-test-runs-on-continue

Conversation

@iagodahlem

@iagodahlem iagodahlem commented Jun 30, 2026

Copy link
Copy Markdown
Member

What

On the Test step of the self-serve SSO configuration flow (<ConfigureSSO />), Continue gated on the locally-cached success probe. If a successful test run completed in a different browser tab, the local probe was stale, so Continue showed "requires a successful test run" and blocked until the user clicked Refresh logs. Continue now revalidates the success probe and gates on the fresh result, so a run completed elsewhere is recognized without a manual refresh.

If the local probe already shows success, Continue still advances immediately (no extra round-trip); the revalidate path only runs when the local state says "no success yet".

Notes

  • To gate Continue on the genuinely fresh probe value (rather than a stale closure / not-yet-updated ref), the internal __internal_useOrganizationEnterpriseConnectionTestRuns revalidate now returns the freshly-fetched page (was void). Internal (__internal_) API; the only consumer is @clerk/ui.

Tests

Continue revalidates and advances when a successful run exists only server-side; a loading state on the button while the probe revalidates; the inline error when there is genuinely no successful run.

ORGS-1701

Summary by CodeRabbit

  • New Features

    • The SSO Test step now re-checks whether test runs succeeded when you click Continue, so success completed in another tab is recognized immediately.
    • The Continue button now reflects a loading/validating state while the success check is refreshed.
  • Bug Fixes

    • The flow advances only after the refreshed check confirms there is at least one successful test run.
    • If no successful run is found, the step shows the localized “no successful test run” error; if the check fails, the step displays the relevant error.

Continue on the Test step gated on a locally-cached success probe. A
test run completed in a different browser tab left that probe stale, so
Continue showed the "needs a successful test run" error and blocked
until the user clicked "Refresh logs" first.

Continue now revalidates the success probe and gates on the freshly
fetched result, advancing as soon as a run that succeeded elsewhere is
picked up. The button shows a loading state while revalidating and still
surfaces the inline error when there is genuinely no successful run.
@vercel

vercel Bot commented Jun 30, 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 30, 2026 8:27pm
swingset Ready Ready Preview, Comment Jun 30, 2026 8:27pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 30, 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

The shared test-run hook now returns fresh page data from revalidate. The UI exposes a probe revalidation boolean through organization connection state, and the Configure SSO test step uses it before advancing. Tests and the changeset were updated accordingly.

Changes

SSO test-run revalidation

Layer / File(s) Summary
Shared hook: revalidate returns data
packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx, packages/shared/src/react/hooks/index.ts, packages/shared/src/react/hooks/__tests__/useOrganizationEnterpriseConnectionTestRuns.spec.tsx
Adds a typed revalidate result, supports exact invalidation, returns cached { data, totalCount }, and covers exact versus broad invalidation behavior in tests.
UI hook: probe revalidation boolean
packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts, packages/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx
Adds revalidateHasSuccessfulTestRun, revalidates only the probe query, and verifies the probe path is used without calling the list revalidation.
Organization connection wiring
packages/ui/src/components/ConfigureSSO/hooks/useOrganizationEnterpriseConnection.ts
Extends TestRunsView and the memoized testRuns object to expose revalidateHasSuccessfulTestRun.
Continue button: async revalidation
packages/ui/src/components/ConfigureSSO/steps/TestConfigurationStep.tsx, packages/ui/src/components/ConfigureSSO/steps/__tests__/TestConfigurationStep.test.tsx
Passes the new revalidation function into the continue button, revalidates before advancing, and covers success, pending, and no-success cases in tests.
Changeset
.changeset/lucky-pears-doubt.md
Patch changeset for @clerk/shared and @clerk/ui documenting the SSO test-step revalidation behavior.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ContinueTestSsoStepButton
  participant useOrganizationEnterpriseConnection
  participant useEnterpriseConnectionTestRuns
  participant QueryClient
  participant Wizard

  User->>ContinueTestSsoStepButton: click Continue
  ContinueTestSsoStepButton->>useOrganizationEnterpriseConnection: read testRuns.revalidateHasSuccessfulTestRun
  useOrganizationEnterpriseConnection->>useEnterpriseConnectionTestRuns: call revalidateHasSuccessfulTestRun()
  useEnterpriseConnectionTestRuns->>QueryClient: revalidateProbe({ armPolling: false, exact: true })
  QueryClient-->>useEnterpriseConnectionTestRuns: fresh probe data
  useEnterpriseConnectionTestRuns-->>ContinueTestSsoStepButton: boolean result
  alt result is true
    ContinueTestSsoStepButton->>Wizard: goNext()
  else
    ContinueTestSsoStepButton->>ContinueTestSsoStepButton: set no-success error
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • wobsoriano

Poem

🐇 I hop to the gate and peek once more,
Fresh test-run sparkles slip through the door.
If success is new, I bounce ahead,
If not, I twitch my nose and stay instead.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main UI behavior change: revalidating test runs before advancing from the Test step.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@pkg-pr-new

pkg-pr-new Bot commented Jun 30, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

npm i https://pkg.pr.new/@clerk/electron@9046

@clerk/electron-passkeys

npm i https://pkg.pr.new/@clerk/electron-passkeys@9046

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 584c4df

@iagodahlem iagodahlem marked this pull request as ready for review June 30, 2026 18:00
@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-30T20:29:49.212Z

Summary

Metric Count
Packages analyzed 19
Packages with changes 1
🔴 Breaking changes 25
🟡 Non-breaking changes 1
🟢 Additions 1

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

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

🔴 Breaking changes index (25)

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

Package Subpath Change
@clerk/shared ./internal/clerk-js/constants ERROR_CODES
@clerk/shared ./react UseOrganizationEnterpriseConnectionTestRunsReturn
@clerk/shared ./types BillingCreditBalanceJSON
@clerk/shared ./types BillingCreditBalanceJSON.balance
@clerk/shared ./types BillingCreditBalanceJSON.object
@clerk/shared ./types BillingCreditBalanceResource
@clerk/shared ./types BillingCreditBalanceResource.balance
@clerk/shared ./types BillingCreditLedgerJSON
@clerk/shared ./types BillingCreditLedgerJSON.amount
@clerk/shared ./types BillingCreditLedgerJSON.created_at
@clerk/shared ./types BillingCreditLedgerJSON.id
@clerk/shared ./types BillingCreditLedgerJSON.object
@clerk/shared ./types BillingCreditLedgerJSON.source_id
@clerk/shared ./types BillingCreditLedgerJSON.source_type
@clerk/shared ./types BillingCreditLedgerResource
@clerk/shared ./types BillingCreditLedgerResource.amount
@clerk/shared ./types BillingCreditLedgerResource.createdAt
@clerk/shared ./types BillingCreditLedgerResource.id
@clerk/shared ./types BillingCreditLedgerResource.sourceId
@clerk/shared ./types BillingCreditLedgerResource.sourceType
@clerk/shared ./types BillingNamespace.getCreditBalance
@clerk/shared ./types BillingNamespace.getCreditHistory
@clerk/shared ./types GetCreditBalanceParams
@clerk/shared ./types GetCreditHistoryParams
@clerk/shared ./types ProfileSectionId

@clerk/shared

Current version: 4.22.1
Recommended bump: MAJOR → 5.0.0

Subpath ./internal/clerk-js/constants

🔴 Breaking Changes (1)

Changed: ERROR_CODES
// ... 9 unchanged lines elided ...
    readonly SAML_USER_ATTRIBUTE_MISSING: "saml_user_attribute_missing";
    readonly USER_LOCKED: "user_locked";
    readonly EXTERNAL_ACCOUNT_NOT_FOUND: "external_account_not_found";
-   readonly EXTERNAL_ACCOUNT_EXISTS: "external_account_exists";
    readonly SESSION_EXISTS: "session_exists";
    readonly SIGN_UP_MODE_RESTRICTED: "sign_up_mode_restricted";
    readonly SIGN_UP_MODE_RESTRICTED_WAITLIST: "sign_up_restricted_waitlist";
// ... 13 unchanged lines elided ...

Static analyzer: Breaking change in variable ERROR_CODES: Type changed: {readonly FORM_IDENTIFIER_NOT_FOUND:"form_identifier_not_found";readonly FORM_PASSWORD_INCORRECT:"form_password_incorre…{readonly FORM_IDENTIFIER_NOT_FOUND:"form_identifier_not_found";readonly FORM_PASSWORD_INCORRECT:"form_password_incorre…

🤖 AI review (confirmed) (95%): The EXTERNAL_ACCOUNT_EXISTS key ("external_account_exists") present in the baseline ERROR_CODES object was removed in the current version; consumers referencing ERROR_CODES.EXTERNAL_ACCOUNT_EXISTS will fail to compile.

Migration: Replace any references to ERROR_CODES.EXTERNAL_ACCOUNT_EXISTS with the appropriate updated error code or handle the account-exists case via an alternative mechanism.

Subpath ./react

🔴 Breaking Changes (1)

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

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) (90%): The revalidate function previously returned Promise<void>; it now returns Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult>. Callers who await or chain .then() on the returned promise and type the result as void will still compile, but callers who explicitly typed the return as Promise<void> or passed the function reference to a () => Promise<void> slot will get a type error because the function's return type is now a wider, non-void type.

Migration: Update any code that references or assigns revalidate with an explicit () => Promise<void> type annotation to accept () => Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult> instead.

🟢 Additions (1)

Added: UseOrganizationEnterpriseConnectionTestRunsRevalidateResult
+ type UseOrganizationEnterpriseConnectionTestRunsRevalidateResult = {
+   data: EnterpriseConnectionTestRunResource[] | undefined;
+   totalCount: number | undefined;
+ };

Added type alias UseOrganizationEnterpriseConnectionTestRunsRevalidateResult

Subpath ./types

🔴 Breaking Changes (23)

Changed: BillingCreditBalanceJSON
- interface BillingCreditBalanceJSON

Static analyzer: Removed interface BillingCreditBalanceJSON

🤖 AI review (confirmed) (95%): The BillingCreditBalanceJSON interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditBalanceJSON from your code.

Changed: BillingCreditBalanceJSON.balance
- balance: BillingMoneyAmountJSON | null;

Static analyzer: Removed property BillingCreditBalanceJSON.balance

🤖 AI review (confirmed) (95%): The balance property of the removed BillingCreditBalanceJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceJSON.balance from your code.

Changed: BillingCreditBalanceJSON.object
- object: 'commerce_credit_balance';

Static analyzer: Removed property BillingCreditBalanceJSON.object

🤖 AI review (confirmed) (95%): The object property of the removed BillingCreditBalanceJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceJSON.object from your code.

Changed: BillingCreditBalanceResource
- interface BillingCreditBalanceResource

Static analyzer: Removed interface BillingCreditBalanceResource

🤖 AI review (confirmed) (95%): The BillingCreditBalanceResource interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditBalanceResource from your code.

Changed: BillingCreditBalanceResource.balance
- balance: BillingMoneyAmount | null;

Static analyzer: Removed property BillingCreditBalanceResource.balance

🤖 AI review (confirmed) (95%): The balance property of the removed BillingCreditBalanceResource interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceResource.balance from your code.

Changed: BillingCreditLedgerJSON
- interface BillingCreditLedgerJSON

Static analyzer: Removed interface BillingCreditLedgerJSON

🤖 AI review (confirmed) (95%): The BillingCreditLedgerJSON interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditLedgerJSON from your code.

Changed: BillingCreditLedgerJSON.amount
- amount: BillingMoneyAmountJSON;

Static analyzer: Removed property BillingCreditLedgerJSON.amount

🤖 AI review (confirmed) (95%): The amount property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.amount from your code.

Changed: BillingCreditLedgerJSON.created_at
- created_at: number;

Static analyzer: Removed property BillingCreditLedgerJSON.created_at

🤖 AI review (confirmed) (95%): The created_at property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.created_at from your code.

Changed: BillingCreditLedgerJSON.id
- id: string;

Static analyzer: Removed property BillingCreditLedgerJSON.id

🤖 AI review (confirmed) (95%): The id property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.id from your code.

Changed: BillingCreditLedgerJSON.object
- object: 'commerce_credit_ledger';

Static analyzer: Removed property BillingCreditLedgerJSON.object

🤖 AI review (confirmed) (95%): The object property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.object from your code.

Changed: BillingCreditLedgerJSON.source_id
- source_id: string;

Static analyzer: Removed property BillingCreditLedgerJSON.source_id

🤖 AI review (confirmed) (95%): The source_id property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.source_id from your code.

Changed: BillingCreditLedgerJSON.source_type
- source_type: string;

Static analyzer: Removed property BillingCreditLedgerJSON.source_type

🤖 AI review (confirmed) (95%): The source_type property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.source_type from your code.

Changed: BillingCreditLedgerResource
- interface BillingCreditLedgerResource

Static analyzer: Removed interface BillingCreditLedgerResource

🤖 AI review (confirmed) (95%): The BillingCreditLedgerResource interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditLedgerResource from your code.

Changed: BillingCreditLedgerResource.amount
- amount: BillingMoneyAmount;

Static analyzer: Removed property BillingCreditLedgerResource.amount

🤖 AI review (confirmed) (95%): The amount property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.amount from your code.

Changed: BillingCreditLedgerResource.createdAt
- createdAt: Date;

Static analyzer: Removed property BillingCreditLedgerResource.createdAt

🤖 AI review (confirmed) (95%): The createdAt property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.createdAt from your code.

Changed: BillingCreditLedgerResource.id
- id: string;

Static analyzer: Removed property BillingCreditLedgerResource.id

🤖 AI review (confirmed) (95%): The id property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.id from your code.

Changed: BillingCreditLedgerResource.sourceId
- sourceId: string;

Static analyzer: Removed property BillingCreditLedgerResource.sourceId

🤖 AI review (confirmed) (95%): The sourceId property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.sourceId from your code.

Changed: BillingCreditLedgerResource.sourceType
- sourceType: string;

Static analyzer: Removed property BillingCreditLedgerResource.sourceType

🤖 AI review (confirmed) (95%): The sourceType property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.sourceType from your code.

Changed: BillingNamespace.getCreditBalance
- getCreditBalance: (params: GetCreditBalanceParams) => Promise<BillingCreditBalanceResource>;

Static analyzer: Removed property BillingNamespace.getCreditBalance

🤖 AI review (confirmed) (95%): The getCreditBalance method is removed from BillingNamespace, breaking any consumer that calls it.

Migration: Remove calls to getCreditBalance or find an alternative API for retrieving credit balance.

Changed: BillingNamespace.getCreditHistory
- getCreditHistory: (params: GetCreditHistoryParams) => Promise<ClerkPaginatedResponse<BillingCreditLedgerResource>>;

Static analyzer: Removed property BillingNamespace.getCreditHistory

🤖 AI review (confirmed) (95%): The getCreditHistory method is removed from BillingNamespace, breaking any consumer that calls it.

Migration: Remove calls to getCreditHistory or find an alternative API for retrieving credit history.

Changed: GetCreditBalanceParams
- type GetCreditBalanceParams = {
-   orgId?: string;
- };

Static analyzer: Removed type alias GetCreditBalanceParams

🤖 AI review (confirmed) (95%): The GetCreditBalanceParams type alias is removed, breaking any consumer code that imports or uses it.

Migration: Remove all imports and references to GetCreditBalanceParams from your code.

Changed: GetCreditHistoryParams
- type GetCreditHistoryParams = {
-   orgId?: string;
- };

Static analyzer: Removed type alias GetCreditHistoryParams

🤖 AI review (confirmed) (95%): The GetCreditHistoryParams type alias is removed, breaking any consumer code that imports or uses it.

Migration: Remove all imports and references to GetCreditHistoryParams from your code.

Changed: ProfileSectionId
- type ProfileSectionId = 'profile' | 'username' | 'emailAddresses' | 'phoneNumbers' | 'connectedAccounts' | 'enterpriseAccounts' | 'web3Wallets' | 'password' | 'passkeys' | 'mfa' | 'danger' | 'activeDevices' | 'organizationProfile' | 'organizationDanger' | 'organizationDomains' | 'manageVerifiedDomains' | 'subscriptionsList' | 'paymentMethods' | 'sso' | 'ssoStatus' | 'enableSso' | 'ssoDomain' | 'ssoConfiguration' | 'configureAgain' | 'resetSso' | 'testSsoUrl' | 'testResults' | 'accountCredits';
+ type ProfileSectionId = 'profile' | 'username' | 'emailAddresses' | 'phoneNumbers' | 'connectedAccounts' | 'enterpriseAccounts' | 'web3Wallets' | 'password' | 'passkeys' | 'mfa' | 'danger' | 'activeDevices' | 'organizationProfile' | 'organizationDanger' | 'organizationDomains' | 'manageVerifiedDomains' | 'subscriptionsList' | 'paymentMethods' | 'sso' | 'ssoStatus' | 'enableSso' | 'ssoDomain' | 'ssoConfiguration' | 'configureAgain' | 'resetSso' | 'testSsoUrl' | 'testResults';

Static analyzer: Breaking change in type alias ProfileSectionId: Type changed: 'accountCredits'|'activeDevices'|'configureAgain'|'connectedAccounts'|'danger'|'emailAddresses'|'enableSso'|'enterprise…'activeDevices'|'configureAgain'|'connectedAccounts'|'danger'|'emailAddresses'|'enableSso'|'enterpriseAccounts'|'manage…

🤖 AI review (confirmed) (95%): The 'accountCredits' variant was removed from the ProfileSectionId union, and ProfileSectionId is used as part of MenuId, so any consumer passing 'accountCredits' as a ProfileSectionId or MenuId will get a type error.

Migration: Remove any usage of 'accountCredits' as a ProfileSectionId or MenuId value in your code.

🟡 Non-breaking Changes (1)

Modified: __internal_LocalizationResource
Diff (before: 1965 lines, after: 1947 lines). Click to expand.
// ... 845 unchanged lines elided ...
      };
      billingPage: {
        title: LocalizationValue;
-       accountCreditsSection: {
-         title: LocalizationValue;
-         viewHistory: LocalizationValue;
-       };
-       creditHistoryPage: {
-         title: LocalizationValue;
-         tableHeader__amount: LocalizationValue;
-         tableHeader__date: LocalizationValue;
-       };
        start: {
          headerTitle__payments: LocalizationValue;
          headerTitle__plans: LocalizationValue;
          headerTitle__subscriptions: LocalizationValue;
          headerTitle__statements: LocalizationValue;
        };
        statementsSection: {
          empty: LocalizationValue;
          itemCaption__paidForPlan: LocalizationValue;
          itemCaption__proratedCredit: LocalizationValue;
          itemCaption__payerCredit: LocalizationValue;
          itemCaption__subscribedAndPaidForPlan: LocalizationValue;
          notFound: LocalizationValue;
          tableHeader__date: LocalizationValue;
          tableHeader__amount: LocalizationValue;
          title: LocalizationValue;
          totalPaid: LocalizationValue;
        };
        switchPlansSection: {
          title: LocalizationValue;
        };
        subscriptionsListSection: {
          tableHeader__plan: LocalizationValue;
          tableHeader__startDate: LocalizationValue;
          tableHeader__edit: LocalizationValue;
          title: LocalizationValue;
          actionLabel__newSubscription: LocalizationValue;
          actionLabel__manageSubscription: LocalizationValue;
          actionLabel__switchPlan: LocalizationValue;
          overview: LocalizationValue;
        };
        paymentHistorySection: {
          empty: LocalizationValue;
          notFound: LocalizationValue;
          tableHeader__date: LocalizationValue;
          tableHeader__amount: LocalizationValue;
          tableHeader__status: LocalizationValue;
        };
        paymentMethodsSection: {
          title: LocalizationValue;
          add: LocalizationValue;
          addSubtitle: LocalizationValue;
          cancelButton: LocalizationValue;
          actionLabel__default: LocalizationValue;
          actionLabel__remove: LocalizationValue;
          formButtonPrimary__add: LocalizationValue;
          formButtonPrimary__pay: LocalizationValue;
          removeMethod: {
            title: LocalizationValue;
            messageLine1: LocalizationValue<'identifier'>;
            messageLine2: LocalizationValue;
            successMessage: LocalizationValue<'paymentMethod'>;
          };
          payWithTestCardButton: LocalizationValue;
        };
        subscriptionsSection: {
          actionLabel__default: LocalizationValue;
        };
      };
      plansPage: {
        title: LocalizationValue;
        alerts: {
          noPermissionsToManageBilling: LocalizationValue;
        };
      };
    };
    userButton: {
      action__manageAccount: LocalizationValue;
      action__signOut: LocalizationValue;
      action__signOutAll: LocalizationValue;
      action__addAccount: LocalizationValue;
      action__openUserMenu: LocalizationValue;
      action__closeUserMenu: LocalizationValue;
      label__userButtonPopover?: LocalizationValue;
      label__accountActions?: LocalizationValue;
      label__activeSessions?: LocalizationValue;
    };
    organizationSwitcher: {
      personalWorkspace: LocalizationValue;
      notSelected: LocalizationValue;
      action__createOrganization: LocalizationValue;
      action__manageOrganization: LocalizationValue;
      action__invitationAccept: LocalizationValue;
      action__suggestionsAccept: LocalizationValue;
      action__openOrganizationSwitcher: LocalizationValue;
      action__closeOrganizationSwitcher: LocalizationValue;
      suggestionsAcceptedLabel: LocalizationValue;
    };
    impersonationFab: {
      title: LocalizationValue<'identifier'>;
      action__signOut: LocalizationValue;
    };
    organizationProfile: {
      navbar: {
        title: LocalizationValue;
        description: LocalizationValue;
        general: LocalizationValue;
        members: LocalizationValue;
        billing: LocalizationValue;
        apiKeys: LocalizationValue;
        security: LocalizationValue;
      };
      badge__unverified: LocalizationValue;
      badge__automaticInvitation: LocalizationValue;
      badge__automaticSuggestion: LocalizationValue;
      badge__manualInvitation: LocalizationValue;
      badge__enterpriseSso: LocalizationValue;
      start: {
        headerTitle__members: LocalizationValue;
        membershipSeatUsageLabel: LocalizationValue<'count' | 'limit'>;
        headerTitle__general: LocalizationValue;
        profileSection: {
          title: LocalizationValue;
          primaryButton: LocalizationValue;
          uploadAction__title: LocalizationValue;
        };
      };
      profilePage: {
        title: LocalizationValue;
        successMessage: LocalizationValue;
        dangerSection: {
          title: LocalizationValue;
          leaveOrganization: {
            title: LocalizationValue;
            messageLine1: LocalizationValue;
            messageLine2: LocalizationValue;
            successMessage: LocalizationValue;
            actionDescription: LocalizationValue<'organizationName'>;
          };
          deleteOrganization: {
            title: LocalizationValue;
            messageLine1: LocalizationValue;
            messageLine2: LocalizationValue;
            actionDescription: LocalizationValue<'organizationName'>;
            successMessage: LocalizationValue;
          };
        };
        domainSection: {
          title: LocalizationValue;
          subtitle: LocalizationValue;
          primaryButton: LocalizationValue;
          menuAction__verify: LocalizationValue;
          menuAction__remove: LocalizationValue;
          menuAction__manage: LocalizationValue;
        };
      };
      createDomainPage: {
        title: LocalizationValue;
        subtitle: LocalizationValue;
      };
      verifyDomainPage: {
        title: LocalizationValue;
        subtitle: LocalizationValue<'domainName'>;
        subtitleVerificationCodeScreen: LocalizationValue<'emailAddress'>;
        formTitle: LocalizationValue;
        formSubtitle: LocalizationValue;
        resendButton: LocalizationValue;
      };
      verifiedDomainPage: {
        title: LocalizationValue<'domain'>;
        subtitle: LocalizationValue<'domain'>;
        start: {
          headerTitle__enrollment: LocalizationValue;
          headerTitle__danger: LocalizationValue;
        };
        enrollmentTab: {
          subtitle: LocalizationValue;
          manualInvitationOption__label: LocalizationValue;
          manualInvitationOption__description: LocalizationValue;
          automaticInvitationOption__label: LocalizationValue;
          automaticInvitationOption__description: LocalizationValue;
          automaticSuggestionOption__label: LocalizationValue;
          automaticSuggestionOption__description: LocalizationValue;
          calloutInfoLabel: LocalizationValue;
          calloutInvitationCountLabel: LocalizationValue<'count'>;
          calloutSuggestionCountLabel: LocalizationValue<'count'>;
        };
        dangerTab: {
          removeDomainTitle: LocalizationValue;
          removeDomainSubtitle: LocalizationValue;
          removeDomainActionLabel__remove: LocalizationValue;
          calloutInfoLabel: LocalizationValue;
        };
      };
      invitePage: {
        title: LocalizationValue;
        subtitle: LocalizationValue;
        successMessage: LocalizationValue;
        detailsTitle__inviteFailed: LocalizationValue<'email_addresses'>;
        formButtonPrimary__continue: LocalizationValue;
        formButtonPrimary__purchaseSeats: LocalizationValue;
        selectDropdown__role: LocalizationValue;
      };
      removeDomainPage: {
        title: LocalizationValue;
        messageLine1: LocalizationValue<'domain'>;
        messageLine2: LocalizationValue;
        successMessage: LocalizationValue;
      };
      securityPage: {
        title: LocalizationValue;
        removeDialog: {
          title: LocalizationValue;
          subtitle: LocalizationValue;
          confirmButton: LocalizationValue;
        };
        ssoSection: {
          title: LocalizationValue;
          badge__unconfigured: LocalizationValue;
          badge__inProgress: LocalizationValue;
          badge__active: LocalizationValue;
          badge__inactive: LocalizationValue;
          descriptionLine1: LocalizationValue;
          primaryButton__startConfiguration: LocalizationValue;
          primaryButton__continueConfiguration: LocalizationValue;
          domainLabel: LocalizationValue;
          menuAction__edit: LocalizationValue;
          menuAction__activate: LocalizationValue;
          menuAction__deactivate: LocalizationValue;
          menuAction__remove: LocalizationValue;
          tooltip: LocalizationValue<'role'>;
          tooltip__noRole: LocalizationValue;
          tooltipLabel: LocalizationValue;
        };
      };
      membersPage: {
        detailsTitle__emptyRow: LocalizationValue;
        action__invite: LocalizationValue;
        action__search: LocalizationValue;
        start: {
          headerTitle__members: LocalizationValue;
          headerTitle__invitations: LocalizationValue;
          headerTitle__requests: LocalizationValue;
        };
        activeMembersTab: {
          tableHeader__user: LocalizationValue;
          tableHeader__joined: LocalizationValue;
          tableHeader__role: LocalizationValue;
          tableHeader__actions: LocalizationValue;
          menuAction__remove: LocalizationValue;
        };
        invitedMembersTab: {
          tableHeader__invited: LocalizationValue;
          menuAction__revoke: LocalizationValue;
        };
        invitationsTab: {
          table__emptyRow: LocalizationValue;
          autoInvitations: {
            headerTitle: LocalizationValue;
            headerSubtitle: LocalizationValue;
            primaryButton: LocalizationValue;
          };
        };
        requestsTab: {
          tableHeader__requested: LocalizationValue;
          menuAction__approve: LocalizationValue;
          menuAction__reject: LocalizationValue;
          table__emptyRow: LocalizationValue;
          autoSuggestions: {
            headerTitle: LocalizationValue;
            headerSubtitle: LocalizationValue;
            primaryButton: LocalizationValue;
          };
        };
        alerts: {
          roleSetMigrationInProgress: {
            title: LocalizationValue;
            subtitle: LocalizationValue;
          };
        };
      };
      billingPage: {
        title: LocalizationValue;
-       accountCreditsSection: {
-         title: LocalizationValue;
-         viewHistory: LocalizationValue;
-       };
-       creditHistoryPage: {
-         title: LocalizationValue;
-         tableHeader__amount: LocalizationValue;
-         tableHeader__date: LocalizationValue;
-       };
        start: {
          headerTitle__payments: LocalizationValue;
          headerTitle__plans: LocalizationValue;
// ... 823 unchanged lines elided ...

Static analyzer: Breaking change in type alias __internal_LocalizationResource: Type changed: {locale:string;maintenanceMode:import("@clerk/shared").LocalizationValue;roles:{[r:string]:import("@clerk/shared").Loca…{locale:string;maintenanceMode:import("@clerk/shared").LocalizationValue;roles:{[r:string]:import("@clerk/shared").Loca…

🤖 AI review (reclassified as non-breaking) (60%): The before and after snippets are nearly identical large objects; the only visible difference is '1885 lines elided' vs '1867 lines elided', suggesting some fields were removed. However, __internal_LocalizationResource is used only as the base of DeepPartial<DeepLocalizationWithoutObjects<__internal_LocalizationResource>> for LocalizationResource, meaning consumers only read/extend from it rather than constructing exact values of this type. Without being able to confirm which specific fields were removed, but given the internal/output nature, confidence is low that this breaks consumers.


Report generated by Break Check

Last ran on 584c4df.

@iagodahlem iagodahlem force-pushed the iago/orgs-1701-test-runs-on-continue branch from a31554a to da3f2fc Compare June 30, 2026 18:17
@changeset-bot

changeset-bot Bot commented Jun 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 584c4df

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

This PR includes changesets to release 23 packages
Name Type
@clerk/shared Patch
@clerk/ui Patch
@clerk/astro Patch
@clerk/backend Patch
@clerk/chrome-extension Patch
@clerk/clerk-js Patch
@clerk/electron Patch
@clerk/expo-passkeys Patch
@clerk/expo Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/headless Patch
@clerk/hono Patch
@clerk/localizations Patch
@clerk/msw Patch
@clerk/nextjs Patch
@clerk/nuxt Patch
@clerk/react-router Patch
@clerk/react Patch
@clerk/tanstack-react-start Patch
@clerk/testing Patch
@clerk/vue Patch
@clerk/swingset 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

@iagodahlem iagodahlem changed the title fix(ui): revalidate test runs on Continue and stop mid-flow refetches gating the security page fix(ui): revalidate test runs before advancing from the Test step Jun 30, 2026
@iagodahlem iagodahlem requested a review from LauraBeatris June 30, 2026 18:43
The shared test-runs hook's revalidate() invalidated the broad
org+connection key, which prefix-matches every test-runs query for the
connection. On the SSO Test step that meant revalidating the success
probe also refetched the visible test-runs list, spinning the "Refresh
logs" button (bound to the list's isFetching) whenever the user clicked
Continue.

Add an `exact` option to revalidate() that invalidates only the query's
own key, and use it for the Test step's probe-only revalidation on
Continue. The default broad behavior is unchanged, so "Refresh logs"
still refetches both the probe and the list.

@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/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx`:
- Around line 10-11: The test around revalidation is only asserting the spy
calls and never verifies the boolean returned by
revalidateHasSuccessfulTestRun(), so the logic that determines whether a
successful test run exists is untested. Update the
useEnterpriseConnectionTestRuns test to assert the resolved value from
revalidateHasSuccessfulTestRun() for both the no-success and success cases,
using the existing probeRevalidate and listRevalidate mocks to drive each
branch. Focus the assertions on the behavior exposed by
useEnterpriseConnectionTestRuns and TestConfigurationStep rather than only on
implementation details like invocation arguments.
🪄 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 Plus

Run ID: e79adfb9-1707-4186-8b5e-82f102f91f01

📥 Commits

Reviewing files that changed from the base of the PR and between 7450977 and 584c4df.

📒 Files selected for processing (4)
  • packages/shared/src/react/hooks/__tests__/useOrganizationEnterpriseConnectionTestRuns.spec.tsx
  • packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx
  • packages/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx
  • packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts
  • packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx

Comment on lines +10 to +11
const probeRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
const listRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Test never asserts the actual boolean result of revalidateHasSuccessfulTestRun().

The test only verifies that probeRevalidate was invoked with the right options — it never checks what revalidateHasSuccessfulTestRun() resolves to. Since the mock always returns { data: [], totalCount: 0 }, the boolean-derivation logic (the part that actually decides whether Continue can advance) is untested in both the "no successful run" and "successful run found" branches. Per the downstream snippet, TestConfigurationStep directly gates advance() on this resolved value, so an incorrect derivation here would silently break the revalidation feature.

As per coding guidelines, test files must "Verify proper error handling and edge cases" and tests should "Test component behavior, not implementation details" — this test currently only verifies implementation (spy calls), not the resulting behavior.

✅ Suggested additional coverage
 const probeRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
 const listRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
...
 describe('useEnterpriseConnectionTestRuns', () => {
   it('revalidateHasSuccessfulTestRun revalidates ONLY the probe, with exact invalidation', async () => {
     ...
   });
+
+  it('resolves true when the revalidated probe contains a successful run', async () => {
+    probeRevalidate.mockResolvedValueOnce({ data: [{ id: 'run_1', status: 'success' }], totalCount: 1 });
+    const { result } = renderHook(() => useEnterpriseConnectionTestRuns(connection, true));
+
+    let resolved: boolean | undefined;
+    await act(async () => {
+      resolved = await result.current.revalidateHasSuccessfulTestRun();
+    });
+
+    expect(resolved).toBe(true);
+  });
+
+  it('resolves false when the revalidated probe has no successful run', async () => {
+    const { result } = renderHook(() => useEnterpriseConnectionTestRuns(connection, true));
+
+    let resolved: boolean | undefined;
+    await act(async () => {
+      resolved = await result.current.revalidateHasSuccessfulTestRun();
+    });
+
+    expect(resolved).toBe(false);
+  });
 });

Also applies to: 40-54

🤖 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/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx`
around lines 10 - 11, The test around revalidation is only asserting the spy
calls and never verifies the boolean returned by
revalidateHasSuccessfulTestRun(), so the logic that determines whether a
successful test run exists is untested. Update the
useEnterpriseConnectionTestRuns test to assert the resolved value from
revalidateHasSuccessfulTestRun() for both the no-success and success cases,
using the existing probeRevalidate and listRevalidate mocks to drive each
branch. Focus the assertions on the behavior exposed by
useEnterpriseConnectionTestRuns and TestConfigurationStep rather than only on
implementation details like invocation arguments.

Source: Coding guidelines

Sign up for free to 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