From 240a77b1988fd8d5e1334032bc55f8e1fe7556ee Mon Sep 17 00:00:00 2001 From: Michael Novotny Date: Wed, 7 Oct 2026 15:31:48 -0500 Subject: [PATCH 1/4] feat(shared): add isReverificationCanceledError and use American English spelling in JSDoc Add isReverificationCanceledError() and keep isReverificationCancelledError() as a deprecated alias. Re-export the new name from the framework errors entry points and switch internal uses to it. Fix British spellings in public JSDoc. Runtime error codes such as reverification_cancelled are unchanged. Co-Authored-By: Claude Opus 5.5 --- .changeset/american-spelling-jsdoc.md | 9 +++++++++ ...erican-spelling-reverification-canceled.md | 9 +++++++++ .../backend/src/api/endpoints/BillingApi.ts | 2 +- packages/backend/src/tokens/clerkRequest.ts | 2 +- .../google-one-tap/ClerkGoogleOneTapSignIn.ts | 2 +- packages/expo/src/google-one-tap/types.ts | 2 +- .../destructive/destructive.controller.ts | 4 ++-- .../use-reverification-with-state.test.tsx | 6 +++--- .../src/app-router/server/currentUser.ts | 2 +- .../app-router/server/keyless-provider.tsx | 2 +- packages/nextjs/src/client-boundary/hooks.ts | 1 + packages/nextjs/src/errors.ts | 1 + packages/nextjs/src/server/protect.ts | 4 ++-- packages/react-router/src/errors.ts | 1 + packages/react/src/errors.ts | 1 + packages/shared/src/__tests__/error.spec.ts | 19 +++++++++++++++++++ packages/shared/src/devBrowser.ts | 2 +- packages/shared/src/error.ts | 1 + packages/shared/src/errors/helpers.ts | 9 +++++++-- .../src/internal/clerk-js/protectCheck.ts | 2 +- .../src/react/hooks/useReverification.ts | 8 ++++---- packages/shared/src/types/clerk.ts | 8 ++++---- packages/shared/src/types/directorySync.ts | 2 +- packages/shared/src/types/json.ts | 6 +++--- packages/shared/src/types/protectConfig.ts | 2 +- .../app/(clerk)/live/reverification/page.tsx | 6 +++--- .../__snapshots__/exports.test.ts.snap | 1 + packages/tanstack-react-start/src/errors.ts | 1 + packages/ui/src/ClerkUI.ts | 2 +- .../UserProfile/MfaBackupCodeCreateForm.tsx | 4 ++-- packages/ui/src/internal/appearance.ts | 8 ++++---- packages/ui/src/styledSystem/types.ts | 2 +- 32 files changed, 90 insertions(+), 41 deletions(-) create mode 100644 .changeset/american-spelling-jsdoc.md create mode 100644 .changeset/american-spelling-reverification-canceled.md diff --git a/.changeset/american-spelling-jsdoc.md b/.changeset/american-spelling-jsdoc.md new file mode 100644 index 00000000000..e6cb947fb48 --- /dev/null +++ b/.changeset/american-spelling-jsdoc.md @@ -0,0 +1,9 @@ +--- +'@clerk/backend': patch +'@clerk/expo': patch +'@clerk/nextjs': patch +'@clerk/shared': patch +'@clerk/ui': patch +--- + +Use American English spelling in JSDoc comments, such as "canceled", "toward", and "customize". There are no runtime changes. diff --git a/.changeset/american-spelling-reverification-canceled.md b/.changeset/american-spelling-reverification-canceled.md new file mode 100644 index 00000000000..8d703183e02 --- /dev/null +++ b/.changeset/american-spelling-reverification-canceled.md @@ -0,0 +1,9 @@ +--- +'@clerk/shared': minor +'@clerk/react': minor +'@clerk/nextjs': minor +'@clerk/react-router': minor +'@clerk/tanstack-react-start': minor +--- + +Add `isReverificationCanceledError()`, the American English spelling of `isReverificationCancelledError()`. `isReverificationCancelledError()` still works and is now deprecated; it will be removed in a future major version. The error code it checks for, `reverification_cancelled`, is unchanged. diff --git a/packages/backend/src/api/endpoints/BillingApi.ts b/packages/backend/src/api/endpoints/BillingApi.ts index 6011d0c9a26..8ef2b21d923 100644 --- a/packages/backend/src/api/endpoints/BillingApi.ts +++ b/packages/backend/src/api/endpoints/BillingApi.ts @@ -54,7 +54,7 @@ export class BillingAPI extends AbstractAPI { * Cancels the given Subscription Item. * @param subscriptionItemId - The ID of the Subscription Item to cancel. * @param params - The parameters for the request. - * @returns The cancelled [`BillingSubscriptionItem`](https://clerk.com/docs/reference/backend/types/billing-subscription-item) object. + * @returns The canceled [`BillingSubscriptionItem`](https://clerk.com/docs/reference/backend/types/billing-subscription-item) object. * @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change. It is advised to [pin](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes. */ public async cancelSubscriptionItem(subscriptionItemId: string, params?: CancelSubscriptionItemParams) { diff --git a/packages/backend/src/tokens/clerkRequest.ts b/packages/backend/src/tokens/clerkRequest.ts index 5ec9ee63908..1a1de2de4f1 100644 --- a/packages/backend/src/tokens/clerkRequest.ts +++ b/packages/backend/src/tokens/clerkRequest.ts @@ -6,7 +6,7 @@ import { createClerkUrl } from './clerkUrl'; /** * A class that extends the native Request class, - * adds cookies helpers and a normalised clerkUrl that is constructed by using the values found + * adds cookies helpers and a normalized clerkUrl that is constructed by using the values found * in req.headers so it is able to work reliably when the app is running behind a proxy server. */ class ClerkRequest extends Request { diff --git a/packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts b/packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts index e11ec6a2618..b2ac7144685 100644 --- a/packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts +++ b/packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts @@ -22,7 +22,7 @@ function getNativeModule(): NonNullable { } /** - * Check if a response indicates the user cancelled the sign-in flow. + * Check if a response indicates the user canceled the sign-in flow. */ export function isCancelledResponse(response: OneTapResponse): response is CancelledResponse { return response.type === 'cancelled'; diff --git a/packages/expo/src/google-one-tap/types.ts b/packages/expo/src/google-one-tap/types.ts index f42b1adcf76..23d5c78a78e 100644 --- a/packages/expo/src/google-one-tap/types.ts +++ b/packages/expo/src/google-one-tap/types.ts @@ -165,7 +165,7 @@ export type OneTapResponse = OneTapSuccessResponse | CancelledResponse | NoSaved /** * Error codes that can be thrown by the Google Sign-In module. * - * - `SIGN_IN_CANCELLED`: User cancelled the sign-in flow + * - `SIGN_IN_CANCELLED`: User canceled the sign-in flow * - `NO_SAVED_CREDENTIAL_FOUND`: No saved credentials available for One Tap * - `NOT_CONFIGURED`: Module not configured before use * - `GOOGLE_SIGN_IN_ERROR`: Generic Google Sign-In error, including Android provider failures such as an unregistered OAuth client diff --git a/packages/mosaic/src/blocks/destructive/destructive.controller.ts b/packages/mosaic/src/blocks/destructive/destructive.controller.ts index 526639325fe..a05409a56d8 100644 --- a/packages/mosaic/src/blocks/destructive/destructive.controller.ts +++ b/packages/mosaic/src/blocks/destructive/destructive.controller.ts @@ -1,4 +1,4 @@ -import { isReverificationCancelledError } from '@clerk/shared/error'; +import { isReverificationCanceledError } from '@clerk/shared/error'; import { useState } from 'react'; import type { ReverificationController } from '../../features/reverification'; @@ -61,7 +61,7 @@ export function useDestructiveController({ await onDelete(); setDestructiveState({ status: 'closed' }); } catch (error: unknown) { - if (isReverificationCancelledError(error)) { + if (isReverificationCanceledError(error)) { setDestructiveState({ status: 'closed' }); return; } diff --git a/packages/mosaic/src/features/reverification/__tests__/use-reverification-with-state.test.tsx b/packages/mosaic/src/features/reverification/__tests__/use-reverification-with-state.test.tsx index 7a0760ee2eb..bf8ff60f4dc 100644 --- a/packages/mosaic/src/features/reverification/__tests__/use-reverification-with-state.test.tsx +++ b/packages/mosaic/src/features/reverification/__tests__/use-reverification-with-state.test.tsx @@ -1,4 +1,4 @@ -import { ClerkRuntimeError, isClerkRuntimeError, isReverificationCancelledError } from '@clerk/shared/error'; +import { ClerkRuntimeError, isClerkRuntimeError, isReverificationCanceledError } from '@clerk/shared/error'; import type * as SharedReact from '@clerk/shared/react'; import type { SessionVerificationLevel } from '@clerk/shared/types'; import { act, renderHook, waitFor } from '@testing-library/react'; @@ -169,7 +169,7 @@ describe('useReverificationWithState', () => { await act(async () => { await expect(pending).rejects.toMatchObject({ code: 'reverification_cancelled' }); }); - expect(isReverificationCancelledError(await pending.catch(error => error))).toBe(true); + expect(isReverificationCanceledError(await pending.catch(error => error))).toBe(true); expect(result.current[1]).toEqual({ phase: 'settled' }); expect(fetcher).toHaveBeenCalledOnce(); expect(challengeCancel).toHaveBeenCalledOnce(); @@ -278,7 +278,7 @@ describe('useReverificationWithState', () => { }, error => error, ); - expect(isReverificationCancelledError(duringVerification)).toBe(false); + expect(isReverificationCanceledError(duringVerification)).toBe(false); expect(isClerkRuntimeError(duringVerification) && duringVerification.code).toBe(IN_PROGRESS); expect(fetcher).toHaveBeenCalledOnce(); diff --git a/packages/nextjs/src/app-router/server/currentUser.ts b/packages/nextjs/src/app-router/server/currentUser.ts index 530064b050c..c1b96dddd2f 100644 --- a/packages/nextjs/src/app-router/server/currentUser.ts +++ b/packages/nextjs/src/app-router/server/currentUser.ts @@ -13,7 +13,7 @@ type CurrentUserOptions = PendingSessionOptions; * Under the hood, this helper: * - calls `fetch()`, so it is automatically deduped per request. * - uses the [`GET /v1/users/{user_id}`](https://clerk.com/docs/reference/backend-api/tag/users/GET/users/%7Buser_id%7D){{ target: '_blank' }} endpoint. - * - counts towards the [Backend API request rate limit](https://clerk.com/docs/guides/how-clerk-works/system-limits). + * - counts toward the [Backend API request rate limit](https://clerk.com/docs/guides/how-clerk-works/system-limits). * * @param opts - Options to configure the behavior of the helper. * diff --git a/packages/nextjs/src/app-router/server/keyless-provider.tsx b/packages/nextjs/src/app-router/server/keyless-provider.tsx index b2956a2b1e6..ade6af08cf0 100644 --- a/packages/nextjs/src/app-router/server/keyless-provider.tsx +++ b/packages/nextjs/src/app-router/server/keyless-provider.tsx @@ -77,7 +77,7 @@ export const KeylessProvider = async (props: KeylessProviderProps) => { const keylessService = await import('../../server/keyless-node.js').then(mod => mod.keyless()); /** - * Notifying the dashboard should run once. We are controlling this behaviour by caching the result of the request. + * Notifying the dashboard should run once. We are controlling this behavior by caching the result of the request. * If the request fails, it will be considered stale after 10 minutes, otherwise it is cached for 24 hours. */ await clerkDevelopmentCache?.run(() => keylessService.completeOnboarding(), { diff --git a/packages/nextjs/src/client-boundary/hooks.ts b/packages/nextjs/src/client-boundary/hooks.ts index 81be38b570d..3a14c3c67c1 100644 --- a/packages/nextjs/src/client-boundary/hooks.ts +++ b/packages/nextjs/src/client-boundary/hooks.ts @@ -25,6 +25,7 @@ export { isEmailLinkError, isKnownError, isMetamaskError, + isReverificationCanceledError, isReverificationCancelledError, EmailLinkErrorCode, EmailLinkErrorCodeStatus, diff --git a/packages/nextjs/src/errors.ts b/packages/nextjs/src/errors.ts index a077c744700..a64ede74695 100644 --- a/packages/nextjs/src/errors.ts +++ b/packages/nextjs/src/errors.ts @@ -2,6 +2,7 @@ export { isClerkRuntimeError, isEmailLinkError, isKnownError, + isReverificationCanceledError, isReverificationCancelledError, isMetamaskError, EmailLinkErrorCode, diff --git a/packages/nextjs/src/server/protect.ts b/packages/nextjs/src/server/protect.ts index 1e72f7128fd..e36ee7d65fe 100644 --- a/packages/nextjs/src/server/protect.ts +++ b/packages/nextjs/src/server/protect.ts @@ -92,7 +92,7 @@ export function createProtect(opts: { /** * middleware and pages throw a notFound error if signed out * but the middleware needs to throw an error it can catch - * use this callback to customise the behavior + * use this callback to customize the behavior */ notFound: () => never; /** @@ -106,7 +106,7 @@ export function createProtect(opts: { /** * protect() in middleware redirects to signInUrl if signed out * protect() in pages throws a notFound error if signed out - * use this callback to customise the behavior + * use this callback to customize the behavior */ redirectToSignIn: RedirectFun; }): AuthProtect { diff --git a/packages/react-router/src/errors.ts b/packages/react-router/src/errors.ts index b8c09e08216..cdf976ef70d 100644 --- a/packages/react-router/src/errors.ts +++ b/packages/react-router/src/errors.ts @@ -7,6 +7,7 @@ export { isEmailLinkError, isKnownError, isMetamaskError, + isReverificationCanceledError, isReverificationCancelledError, EmailLinkErrorCode, EmailLinkErrorCodeStatus, diff --git a/packages/react/src/errors.ts b/packages/react/src/errors.ts index 2b6459b810e..36c8df1b8ad 100644 --- a/packages/react/src/errors.ts +++ b/packages/react/src/errors.ts @@ -7,6 +7,7 @@ export { isEmailLinkError, isKnownError, isMetamaskError, + isReverificationCanceledError, isReverificationCancelledError, EmailLinkErrorCode, EmailLinkErrorCodeStatus, diff --git a/packages/shared/src/__tests__/error.spec.ts b/packages/shared/src/__tests__/error.spec.ts index 0588a852a94..986e00b9635 100644 --- a/packages/shared/src/__tests__/error.spec.ts +++ b/packages/shared/src/__tests__/error.spec.ts @@ -8,6 +8,8 @@ import { is4xxError, is429Error, isClerkRuntimeError, + isReverificationCanceledError, + isReverificationCancelledError, isUnauthenticatedError, } from '../error'; @@ -142,6 +144,23 @@ describe('isUnauthenticatedError', () => { }); }); +describe('isReverificationCanceledError', () => { + it('returns true for a canceled reverification', () => { + expect(isReverificationCanceledError(new ClerkRuntimeError('canceled', { code: 'reverification_cancelled' }))).toBe( + true, + ); + }); + + it('returns false for other errors', () => { + expect(isReverificationCanceledError(new ClerkRuntimeError('other', { code: 'other_code' }))).toBe(false); + expect(isReverificationCanceledError(new Error('canceled'))).toBe(false); + }); + + it('is also available under the deprecated isReverificationCancelledError name', () => { + expect(isReverificationCancelledError).toBe(isReverificationCanceledError); + }); +}); + describe('ClerkOfflineError', () => { it('is an instance of ClerkRuntimeError', () => { const error = new ClerkOfflineError('Network request failed'); diff --git a/packages/shared/src/devBrowser.ts b/packages/shared/src/devBrowser.ts index f8157217304..c080d9de069 100644 --- a/packages/shared/src/devBrowser.ts +++ b/packages/shared/src/devBrowser.ts @@ -51,7 +51,7 @@ const removeDevBrowserFromURLSearchParams = (_url: URL) => { * the legacy __dev_session from the URL searchParams * We no longer need to use this value, however, we should remove it from the URL * Existing v4 apps will write the dev browser to the hash and the search params in order to ensure - * backwards compatibility with older v4 apps. + * backward compatibility with older v4 apps. * The only use case where this is needed now is when a user upgrades to clerk@5 locally * without changing the component's version on their dashboard. * In this scenario, the AP@4 -> localhost@5 redirect will still have the value in the hash, diff --git a/packages/shared/src/error.ts b/packages/shared/src/error.ts index 37eb5e41bb7..affac87bc6b 100644 --- a/packages/shared/src/error.ts +++ b/packages/shared/src/error.ts @@ -26,6 +26,7 @@ export { isNetworkError, isPasswordPwnedError, isPasswordCompromisedError, + isReverificationCanceledError, isReverificationCancelledError, isUnauthenticatedError, isUnauthorizedError, diff --git a/packages/shared/src/errors/helpers.ts b/packages/shared/src/errors/helpers.ts index 1aeb4c698d7..0a7014f95fb 100644 --- a/packages/shared/src/errors/helpers.ts +++ b/packages/shared/src/errors/helpers.ts @@ -92,12 +92,17 @@ export function isKnownError(error: any): error is ClerkAPIResponseError | Clerk } /** - * Checks if the provided error is a Clerk runtime error indicating a reverification was cancelled. + * Checks if the provided error is a Clerk runtime error indicating a reverification was canceled. */ -export function isReverificationCancelledError(err: any) { +export function isReverificationCanceledError(err: any) { return isClerkRuntimeError(err) && err.code === 'reverification_cancelled'; } +/** + * @deprecated Use `isReverificationCanceledError` instead. This alias will be removed in a future major version. + */ +export const isReverificationCancelledError = isReverificationCanceledError; + /** * Checks if the provided error is a Metamask error. */ diff --git a/packages/shared/src/internal/clerk-js/protectCheck.ts b/packages/shared/src/internal/clerk-js/protectCheck.ts index f44e33596cd..eece38e18aa 100644 --- a/packages/shared/src/internal/clerk-js/protectCheck.ts +++ b/packages/shared/src/internal/clerk-js/protectCheck.ts @@ -73,7 +73,7 @@ function resolveLoadTimeoutMs(configured: number | undefined): number { * host-side wall over execution aborts legitimate challenges and reports them as timeouts, and * since a re-run restarts the work, retrying cannot win on any connection slow enough to trip it. * - * The abort signal is raced too. A stalled import cannot itself be cancelled, but without this the + * The abort signal is raced too. A stalled import cannot itself be canceled, but without this the * caller's abort would not settle anything: an unmounted component would keep this promise, its * closures and its timer alive for the whole load bound, and then report a load failure for what * was really a cancellation. diff --git a/packages/shared/src/react/hooks/useReverification.ts b/packages/shared/src/react/hooks/useReverification.ts index 3ebb8dba3c4..83de09905c5 100644 --- a/packages/shared/src/react/hooks/useReverification.ts +++ b/packages/shared/src/react/hooks/useReverification.ts @@ -39,7 +39,7 @@ type ExcludeClerkError = T extends { clerk_error: any } ? never : T; */ export type NeedsReverificationParameters = { /** - * Marks the reverification process as cancelled and rejects the original request. + * Marks the reverification process as canceled and rejects the original request. */ cancel: () => void; /** @@ -189,7 +189,7 @@ function createReverificationHandler(params: CreateReverificationHandlerParams) * * ```tsx {{ filename: 'src/components/MyButton.tsx' }} * import { useReverification } from '@clerk/react' - * import { isReverificationCancelledError } from '@clerk/react/error' + * import { isReverificationCanceledError } from '@clerk/react/errors' * * type MyData = { * balance: number @@ -203,11 +203,11 @@ function createReverificationHandler(params: CreateReverificationHandlerParams) * try { * const myData = await enhancedFetcher() * // ^ is types as `MyData` - * } catch (e) { + * } catch (err) { * // Handle error returned from the fetcher here * * // You can also handle cancellation with the following - * if (isReverificationCancelledError(err)) { + * if (isReverificationCanceledError(err)) { * // Handle the cancellation error here * } * } diff --git a/packages/shared/src/types/clerk.ts b/packages/shared/src/types/clerk.ts index ec23d03de7a..d4806e675a4 100644 --- a/packages/shared/src/types/clerk.ts +++ b/packages/shared/src/types/clerk.ts @@ -1010,7 +1010,7 @@ export interface Clerk { /** * Decorates the provided URL with the auth token for development instances. * - * @param to - The route to create a URL towards. + * @param to - The route to create a URL toward. */ buildUrlWithAuth(to: string): string; @@ -1748,7 +1748,7 @@ type NavigationType = */ | 'external' /** - * Window navigations are navigations towards a different origin + * Window navigations are navigations toward a different origin * and are not handled by the Clerk component or the host app router. */ | 'window'; @@ -1970,7 +1970,7 @@ export type __internal_UserVerificationProps = RoutingOptions & { afterVerification?: () => void; /** - * Non-awaitable callback for when verification is cancelled, (i.e modal is closed) + * Non-awaitable callback for when verification is canceled, (i.e modal is closed) */ afterVerificationCancelled?: () => void; @@ -2051,7 +2051,7 @@ export type GoogleOneTapProps = GoogleOneTapRedirectUrlProps & { /** * FedCM enables more private sign-in flows without requiring the use of third-party cookies. * The browser controls user settings, displays user prompts, and only contacts an Identity Provider such as Google after explicit user consent is given. - * Backwards compatible with browsers that still support third-party cookies. + * Backward compatible with browsers that still support third-party cookies. * * @default true */ diff --git a/packages/shared/src/types/directorySync.ts b/packages/shared/src/types/directorySync.ts index e8340764fcd..1b5687d41a0 100644 --- a/packages/shared/src/types/directorySync.ts +++ b/packages/shared/src/types/directorySync.ts @@ -25,7 +25,7 @@ export interface DirectorySyncJSON extends ClerkResourceJSON { credentials_configured?: boolean | null; /** * The SCIM bearer token. Only present on create and rotate responses; it - * cannot be retrieved again afterwards. + * cannot be retrieved again afterward. */ api_key?: string | null; created_at: number; diff --git a/packages/shared/src/types/json.ts b/packages/shared/src/types/json.ts index 9c0fd4e5cba..a05b8d109e1 100644 --- a/packages/shared/src/types/json.ts +++ b/packages/shared/src/types/json.ts @@ -991,7 +991,7 @@ export interface BillingSubscriptionJSON extends ClerkResourceJSON { object: 'commerce_subscription'; id: string; /** - * Describes the details for the next payment cycle. It is `undefined` for subscription items that are cancelled or on the free plan, and `null` when there is no upcoming payment. + * Describes the details for the next payment cycle. It is `undefined` for subscription items that are canceled or on the free plan, and `null` when there is no upcoming payment. */ next_payment?: BillingSubscriptionNextPaymentJSON | null; /** @@ -1170,7 +1170,7 @@ export interface BillingCheckoutTotalsJSON { per_unit_totals?: BillingPerUnitTotalJSON[]; total_due_now: BillingMoneyAmountJSON; /** - * Legacy credit field. Kept for backwards compatibility; prefer the unified `credits` breakdown. + * Legacy credit field. Kept for backward compatibility; prefer the unified `credits` breakdown. */ credit: BillingMoneyAmountJSON | null; credits: BillingCreditsJSON | null; @@ -1183,7 +1183,7 @@ export interface BillingCheckoutTotalsJSON { discounts: BillingDiscountsJSON | null; /** * The expected recurring payment for each future billing period. - * Kept for backwards compatibility. Prefer `totals_due_per_period` for the full breakdown. + * Kept for backward compatibility. Prefer `totals_due_per_period` for the full breakdown. */ total_due_per_period: BillingMoneyAmountJSON; /** diff --git a/packages/shared/src/types/protectConfig.ts b/packages/shared/src/types/protectConfig.ts index 45ea9c20f01..cc16852ace9 100644 --- a/packages/shared/src/types/protectConfig.ts +++ b/packages/shared/src/types/protectConfig.ts @@ -17,7 +17,7 @@ export interface ProtectLoader { type: string; /** * Attribute values may contain the placeholders `{cid}`, `{pid}`, `{rid}` and `{sdkver}`, which - * are substituted before the element is appended. An unrecognised `{…}` is left verbatim. + * are substituted before the element is appended. An unrecognized `{…}` is left verbatim. * `{sdkver}` is how the server tells a build that interpolates from one that does not, so a * loader wanting the current response shape has to carry it. */ diff --git a/packages/swingset/src/app/(clerk)/live/reverification/page.tsx b/packages/swingset/src/app/(clerk)/live/reverification/page.tsx index a896b4bff6b..db0731bbbfd 100644 --- a/packages/swingset/src/app/(clerk)/live/reverification/page.tsx +++ b/packages/swingset/src/app/(clerk)/live/reverification/page.tsx @@ -9,7 +9,7 @@ import { Flow, type FlowDirection } from '@clerk/mosaic/components/flow'; import { Reverification, useReverificationFlow } from '@clerk/mosaic/features/reverification'; import { MosaicProvider } from '@clerk/mosaic/mosaic-provider'; import { useUser } from '@clerk/nextjs'; -import { isClerkRuntimeError, isReverificationCancelledError } from '@clerk/shared/error'; +import { isClerkRuntimeError, isReverificationCanceledError } from '@clerk/shared/error'; import Link from 'next/link'; import { useRef, useState } from 'react'; @@ -68,7 +68,7 @@ function CardHarness() { return; } await resetMockDelete(); - if (isReverificationCancelledError(error)) { + if (isReverificationCanceledError(error)) { setStatus('cancelled'); setMessage('Reverification cancelled.'); return; @@ -140,7 +140,7 @@ function DialogHarness() { return; } await resetMockDelete(); - if (isReverificationCancelledError(error)) { + if (isReverificationCanceledError(error)) { setErrorMessage(null); setOpen(false); return; diff --git a/packages/tanstack-react-start/src/__tests__/__snapshots__/exports.test.ts.snap b/packages/tanstack-react-start/src/__tests__/__snapshots__/exports.test.ts.snap index bc8dc12e555..daa015259a4 100644 --- a/packages/tanstack-react-start/src/__tests__/__snapshots__/exports.test.ts.snap +++ b/packages/tanstack-react-start/src/__tests__/__snapshots__/exports.test.ts.snap @@ -9,6 +9,7 @@ exports[`errors public exports > should not change unexpectedly 1`] = ` "isEmailLinkError", "isKnownError", "isMetamaskError", + "isReverificationCanceledError", "isReverificationCancelledError", ] `; diff --git a/packages/tanstack-react-start/src/errors.ts b/packages/tanstack-react-start/src/errors.ts index 6381944d22c..0df5ba4bd6c 100644 --- a/packages/tanstack-react-start/src/errors.ts +++ b/packages/tanstack-react-start/src/errors.ts @@ -4,6 +4,7 @@ export { isEmailLinkError, isKnownError, isMetamaskError, + isReverificationCanceledError, isReverificationCancelledError, EmailLinkErrorCode, EmailLinkErrorCodeStatus, diff --git a/packages/ui/src/ClerkUI.ts b/packages/ui/src/ClerkUI.ts index c6bee5da754..3807a8fb261 100644 --- a/packages/ui/src/ClerkUI.ts +++ b/packages/ui/src/ClerkUI.ts @@ -85,7 +85,7 @@ export class ClerkUI implements ClerkUIInstance { * Ensures the UI component renderer is mounted and ready. * * Returns a promise that resolves with {@link ComponentControls} once the - * renderer is fully initialised. Subsequent calls return the same promise. + * renderer is fully initialized. Subsequent calls return the same promise. * * @param opts - Optional hints for the renderer. * @param opts.preloadHint - An optional component name to preload assets for (e.g. `"SignIn"`). diff --git a/packages/ui/src/components/UserProfile/MfaBackupCodeCreateForm.tsx b/packages/ui/src/components/UserProfile/MfaBackupCodeCreateForm.tsx index e542a62ab2a..894bf7bea89 100644 --- a/packages/ui/src/components/UserProfile/MfaBackupCodeCreateForm.tsx +++ b/packages/ui/src/components/UserProfile/MfaBackupCodeCreateForm.tsx @@ -1,4 +1,4 @@ -import { isReverificationCancelledError } from '@clerk/shared/error'; +import { isReverificationCanceledError } from '@clerk/shared/error'; import { useReverification, useUser } from '@clerk/shared/react'; import type { BackupCodeResource } from '@clerk/shared/types'; import React from 'react'; @@ -30,7 +30,7 @@ export const MfaBackupCodeCreateForm = withCardStateProvider((props: MfaBackupCo void createBackupCode() .then(backupCode => setBackupCode(backupCode)) .catch(err => { - if (isReverificationCancelledError(err)) { + if (isReverificationCanceledError(err)) { return onReset(); } diff --git a/packages/ui/src/internal/appearance.ts b/packages/ui/src/internal/appearance.ts index 09fc4ffad1a..c16cbaf6ddc 100644 --- a/packages/ui/src/internal/appearance.ts +++ b/packages/ui/src/internal/appearance.ts @@ -1071,7 +1071,7 @@ export type Theme = { export type Options = { /** * Controls whether the logo will be rendered inside or outside the component card. - * To customise the logo further, you can use {@link Appearance.elements} + * To customize the logo further, you can use {@link Appearance.elements} * * @default inside */ @@ -1081,7 +1081,7 @@ export type Options = { * By default, the components will use the logo you've set in the Clerk Dashboard. * This option is helpful when you need to display different logos for different themes, * eg: white logo on dark themes, black logo on light themes - * To customise the logo further, you can use {@link Appearance.elements} + * To customize the logo further, you can use {@link Appearance.elements} * * @default undefined */ @@ -1099,14 +1099,14 @@ export type Options = { * Controls the variant that will be used for the social buttons. * By default, the components will use block buttons if you have less than * 3 social providers enabled, otherwise icon buttons will be used. - * To customise the social buttons further, you can use {@link Appearance.elements} + * To customize the social buttons further, you can use {@link Appearance.elements} * * @default auto */ socialButtonsVariant?: 'auto' | 'iconButton' | 'blockButton'; /** * Controls whether the social buttons will be rendered above or below the card form. - * To customise the social button container further, you can use {@link Appearance.elements} + * To customize the social button container further, you can use {@link Appearance.elements} * * @default 'top' */ diff --git a/packages/ui/src/styledSystem/types.ts b/packages/ui/src/styledSystem/types.ts index b46881bf0c3..d247b61d4b3 100644 --- a/packages/ui/src/styledSystem/types.ts +++ b/packages/ui/src/styledSystem/types.ts @@ -8,7 +8,7 @@ type StyleRule = Exclude<_Interpolation, string | number | boolea /** * Primitives can override their styles using the css prop. - * For customising layout/theme, prefer using the props defined on the component. + * For customizing layout/theme, prefer using the props defined on the component. */ type ThemableCssProp = ((params: InternalTheme) => StyleRule) | StyleRule; type CssProp = { css?: ThemableCssProp }; From 855955ea9f5357b6f8a061a7bc220655bad0324b Mon Sep 17 00:00:00 2001 From: Michael Novotny Date: Wed, 7 Oct 2026 16:30:45 -0500 Subject: [PATCH 2/4] chore(repo): add an American spelling check and fix remaining British spellings Vendor the American spelling checker as scripts/american-spelling.mjs, with its test, and run it from pnpm lint and CI as pnpm lint:american-spelling. Public names that can't be renamed outside a major go in EXCEPTIONS. Bare-word external values (GitHub Actions cancelled(), the Electron native 'cancelled' code, the Directory Sync API status, the CSS color grey, native Google messages, French test strings) get ignore-next-line markers. Fix the other 363 offenders with --fix. Co-Authored-By: Claude Opus 5.5 --- .changeset/american-spelling-messages.md | 9 + .claude/skills/clerk-monorepo/SKILL.md | 6 +- .../references/breaking-changes.md | 2 +- .../clerk-monorepo/references/changesets.md | 2 +- .../clerk-monorepo/references/package-map.md | 8 +- .claude/skills/mosaic/SKILL.md | 2 +- .claude/skills/mosaic/references/machines.md | 2 +- .claude/skills/mosaic/references/migration.md | 2 +- .claude/skills/mosaic/references/motion.md | 8 +- .claude/skills/mosaic/references/stylex.md | 14 +- .claude/skills/mosaic/references/views.md | 4 +- .github/workflows/ci.yml | 5 + .github/workflows/e2e-staging.yml | 4 +- .github/workflows/release.yml | 2 + .typedoc/README.md | 4 +- AGENTS.md | 2 +- docs/CONTRIBUTING.md | 6 +- docs/PUBLISH.md | 2 +- integration/README.md | 3 +- integration/tests/reverification.test.ts | 2 +- .../tests/session-tasks-multi-session.test.ts | 2 +- package.json | 1 + packages/backend/src/tokens/request.ts | 2 +- .../clerk-js/src/core/__tests__/clerk.test.ts | 6 +- .../src/core/__tests__/protect.test.ts | 2 +- .../src/core/__tests__/protectSession.test.ts | 18 +- .../src/core/__tests__/tokenCache.test.ts | 6 +- packages/clerk-js/src/core/protect.ts | 2 +- packages/clerk-js/src/core/protectSession.ts | 6 +- .../src/core/resources/OrganizationDomain.ts | 2 +- .../clerk-js/src/core/resources/SignIn.ts | 2 +- .../resources/__tests__/Organization.test.ts | 2 +- .../core/resources/__tests__/Passkey.test.ts | 14 +- .../core/resources/__tests__/SignIn.test.ts | 2 +- packages/clerk-js/src/core/tokenStore.ts | 2 +- .../authenticateWithTransport.test.ts | 4 +- .../src/utils/enterpriseConnection.ts | 2 +- packages/clerk-js/src/utils/mergePatch.ts | 2 +- packages/electron-passkeys/README.md | 2 + .../main/__tests__/passkey-handlers.test.ts | 2 + packages/electron/src/main/oauth-transport.ts | 2 +- .../electron/src/main/passkey-handlers.ts | 1 + .../src/passkeys/__tests__/errors.test.ts | 2 + .../src/passkeys/__tests__/index.test.ts | 9 +- .../electron/src/passkeys/shared/errors.ts | 1 + packages/electron/src/shared/types.ts | 1 + .../src/storage/__tests__/index.test.ts | 2 +- packages/eslint-plugin/README.md | 2 +- .../google-one-tap/ClerkGoogleOneTapSignIn.ts | 19 +- .../__tests__/ClerkGoogleOneTapSignIn.test.ts | 6 +- packages/expo/src/google-one-tap/index.ts | 4 +- packages/expo/src/google-one-tap/types.ts | 6 +- .../__tests__/useSignInWithGoogle.test.ts | 2 +- .../src/hooks/useSignInWithGoogle.shared.ts | 2 +- .../__tests__/useLocalCredentials.test.ts | 4 +- .../expo/src/provider/nativeClientSync.tsx | 2 +- .../provider/singleton/createClerkInstance.ts | 10 +- packages/expo/src/types/index.ts | 2 +- packages/localizations/package.json | 2 +- packages/localizations/src/en-US.ts | 6 +- .../confirmation.controller.test.ts | 2 +- .../src/blocks/confirmation/confirmation.tsx | 4 +- .../destructive.controller.test.ts | 4 +- .../components/action-bar/action-bar.test.tsx | 6 +- .../mosaic/src/components/banner/banner.tsx | 2 +- .../src/components/button/submit-button.tsx | 2 +- .../mosaic/src/components/card/card.test.tsx | 2 +- .../components/dialog/alert-dialog.test.tsx | 2 +- .../src/components/dialog/dialog.styles.ts | 12 +- .../src/components/dialog/dialog.test.tsx | 2 +- .../src/components/field/field.test.tsx | 2 +- packages/mosaic/src/components/menu/menu.tsx | 2 +- .../mosaic/src/components/otp/otp.styles.ts | 2 +- packages/mosaic/src/components/otp/otp.tsx | 2 +- .../src/components/popover/popover.styles.ts | 4 +- .../src/components/profile/profile.test.tsx | 2 +- .../scroll-area/scroll-area.styles.ts | 14 +- .../scroll-area/scroll-area.test.ts | 4 +- .../scroll-area/scroll-area.vars.stylex.ts | 2 +- .../mosaic/src/components/select/select.tsx | 4 +- .../components/tag-input/tag-input.test.tsx | 2 +- .../__tests__/reverification.feature.test.tsx | 10 +- .../use-reverification-with-state.test.tsx | 4 +- .../reverification.controller.ts | 2 +- .../use-reverification-with-state.ts | 4 +- .../__tests__/user-button.layout.test.ts | 2 +- .../user-button/user-button-item.styles.ts | 2 +- .../user-button/user-button.styles.ts | 2 +- .../features/user-button/user-button.types.ts | 2 +- .../features/user-button/user-button.view.tsx | 10 +- ...rofile-danger-section.integration.test.tsx | 2 +- .../user-profile-device-actions.test.tsx | 4 +- .../user-profile-mfa-section.view.test.tsx | 2 +- ...er-profile-active-devices-section.view.tsx | 2 +- ...r-profile-edit-password.controller.test.ts | 2 +- .../src/hooks/use-accessible-name-warning.ts | 4 +- .../mosaic/src/hooks/use-debounced-async.ts | 2 +- packages/mosaic/src/hooks/use-spin-delay.ts | 2 +- .../src/localization/errors.messages.ts | 4 +- packages/mosaic/src/machine/README.md | 4 +- .../__tests__/delete-organization-machine.ts | 4 +- .../src/machine/__tests__/machine.test.ts | 2 +- .../src/machine/__tests__/patterns.test.ts | 12 +- packages/mosaic/src/machine/assign.ts | 2 +- packages/mosaic/src/machine/create-actor.ts | 2 +- packages/mosaic/src/machine/types.ts | 2 +- .../mosaic/src/primitives/button/README.md | 2 +- .../mosaic/src/primitives/button/button.tsx | 2 +- .../mosaic/src/primitives/dialog/README.md | 2 +- .../src/primitives/dialog/dialog-context.ts | 2 +- .../src/primitives/dialog/dialog-root.tsx | 2 +- .../src/primitives/dialog/dialog.test.tsx | 2 +- .../src/primitives/drawer/drawer.test.tsx | 4 +- .../mosaic/src/primitives/drawer/helpers.ts | 4 +- .../src/primitives/drawer/use-drawer-drag.ts | 4 +- .../src/primitives/drawer/use-snap-points.ts | 2 +- .../hooks/use-animations-finished.test.ts | 6 +- .../hooks/use-animations-finished.ts | 2 +- .../src/primitives/hooks/use-focus-target.ts | 2 +- .../hooks/use-transition-status.test.ts | 2 +- .../primitives/hooks/use-transition.test.ts | 2 +- .../src/primitives/hooks/use-transition.ts | 2 +- .../mosaic/src/primitives/otp/otp.test.tsx | 2 +- packages/mosaic/src/props.ts | 2 +- .../mosaic/src/styles/focus-outline.styles.ts | 2 +- .../mosaic/src/styles/focus-outline.test.ts | 4 +- packages/mosaic/src/styles/input.styles.ts | 2 +- .../src/styles/visually-hidden.styles.ts | 2 +- packages/mosaic/src/tokens.stylex.ts | 6 +- packages/msw/request-handlers.ts | 2 +- packages/nextjs/src/server/utils.ts | 2 +- packages/nuxt/src/module.ts | 2 +- .../react/src/components/uiComponents.tsx | 4 +- packages/shared/src/__tests__/error.spec.ts | 2 +- .../clerk-js/__tests__/protectCheck.test.ts | 2 +- .../react/hooks/useOrganizationDomains.tsx | 10 +- .../src/react/hooks/useReverification.ts | 2 +- packages/shared/src/types/directorySync.ts | 1 + packages/shared/src/types/theme.ts | 1 + .../app/(clerk)/live/reverification/page.tsx | 6 +- .../loaders/stylex-inject-named-container.cjs | 2 +- packages/swingset/src/lib/registry.ts | 4 +- packages/swingset/src/stories/action-bar.mdx | 2 +- .../swingset/src/stories/button.stories.tsx | 2 +- .../swingset/src/stories/confirmation.mdx | 4 +- packages/swingset/src/stories/copy-button.mdx | 2 +- .../src/stories/copy-button.stories.tsx | 2 +- packages/swingset/src/stories/destructive.mdx | 4 +- .../swingset/src/stories/dialog.component.mdx | 2 +- .../swingset/src/stories/field.component.mdx | 20 +- .../swingset/src/stories/menu.component.mdx | 2 +- .../swingset/src/stories/otp.component.mdx | 2 +- .../src/stories/popover.component.stories.tsx | 16 +- .../src/stories/profile.component.mdx | 4 +- .../src/stories/profile.component.stories.tsx | 2 +- .../swingset/src/stories/reverification.mdx | 6 +- packages/swingset/src/stories/scroll-area.mdx | 28 +- .../src/stories/scroll-area.stories.tsx | 4 +- .../src/stories/tooltip.component.mdx | 2 +- .../src/stories/tooltip.component.stories.tsx | 10 +- packages/swingset/src/stories/user-button.mdx | 2 +- packages/ui/src/common/SSOCallback.tsx | 6 +- .../ConfigureDirectorySyncContext.tsx | 2 +- .../ConfigureDirectorySync/SyncNowRow.tsx | 3 +- .../ConfigureDirectorySyncWizard.test.tsx | 2 +- .../ConfigureSSO/ConfigureSSOHeader.tsx | 2 +- .../ConfigureSSO.navigation.test.tsx | 2 +- ...eOrganizationEnterpriseConnection.test.tsx | 2 +- .../__tests__/SelectProviderStep.test.tsx | 2 +- .../components/ProtectCheckModal/index.tsx | 4 +- .../SignIn/SignInFactorTwoCodeForm.tsx | 2 +- .../SignIn/SignInFactorTwoEmailLinkCard.tsx | 2 +- .../components/SignIn/SignInProtectCheck.tsx | 4 +- .../ui/src/components/SignIn/SignInStart.tsx | 2 +- .../__tests__/SignInProtectCheck.test.tsx | 6 +- .../__tests__/SignInSocialButtons.test.tsx | 2 +- .../components/SignUp/SignUpProtectCheck.tsx | 4 +- .../__tests__/SignUpSocialButtons.test.tsx | 2 +- .../__tests__/SubscriptionDetails.test.tsx | 4 +- .../components/UserButton/SessionActions.tsx | 4 +- .../__tests__/PasskeysSection.test.tsx | 6 +- .../EnableOrganizationsPrompt/index.tsx | 8 +- packages/ui/src/elements/FormControl.tsx | 2 +- packages/ui/src/elements/Select.tsx | 4 +- .../__tests__/AuthenticationLabels.test.tsx | 4 + .../ui/src/elements/__tests__/Menu.test.tsx | 2 +- .../ui/src/hooks/useProtectCheckRunner.ts | 26 +- .../src/localization/applyTokensToString.ts | 12 +- .../ui/src/primitives/hooks/useFormField.tsx | 2 +- .../src/utils/__tests__/cssLayerName.test.ts | 4 +- .../src/utils/colors/__tests__/legacy.test.ts | 3 + packages/ui/src/utils/colors/legacy.ts | 1 + packages/ui/src/utils/errorHandler.ts | 2 +- packages/upgrade/src/cli.js | 2 +- .../guide-generators/core-2/retheme/index.js | 2 +- scripts/american-spelling.mjs | 619 ++++++++++++++++++ scripts/american-spelling.test.mjs | 117 ++++ scripts/lint.mjs | 11 +- 198 files changed, 1158 insertions(+), 367 deletions(-) create mode 100644 .changeset/american-spelling-messages.md create mode 100644 scripts/american-spelling.mjs create mode 100644 scripts/american-spelling.test.mjs diff --git a/.changeset/american-spelling-messages.md b/.changeset/american-spelling-messages.md new file mode 100644 index 00000000000..1d2a0ac3908 --- /dev/null +++ b/.changeset/american-spelling-messages.md @@ -0,0 +1,9 @@ +--- +'@clerk/localizations': patch +'@clerk/mosaic': patch +'@clerk/shared': patch +'@clerk/electron': patch +'@clerk/upgrade': patch +--- + +Use American English spelling in user-facing text. The `en-US` strings for a canceled Directory Sync run and canceled passkey prompts now read "Canceled" and "canceled", as do the reverification, Electron OAuth, and upgrade CLI messages. Error codes and localization keys are unchanged. diff --git a/.claude/skills/clerk-monorepo/SKILL.md b/.claude/skills/clerk-monorepo/SKILL.md index e5dc8039a59..bcb678b6096 100644 --- a/.claude/skills/clerk-monorepo/SKILL.md +++ b/.claude/skills/clerk-monorepo/SKILL.md @@ -46,8 +46,8 @@ The ~10 packages people touch most. Full 25-package table, the dependency pyrami | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | `@clerk/shared` | Utilities used everywhere (storage, events, React helpers). Most-depended-on; changes fan out to ~20 packages. Types live here too (`@clerk/shared/types`). | | `@clerk/backend` | Server-side: JWT verification, the Backend API REST client, webhooks. Used by every framework adapter. | -| `@clerk/clerk-js` | ⚠️ The browser runtime loaded via script tag. **Backwards-compat sensitive** (see rules). | -| `@clerk/ui` | ⚠️ The React components powering the hosted sign-in / sign-up UI. **Backwards-compat sensitive**. | +| `@clerk/clerk-js` | ⚠️ The browser runtime loaded via script tag. **Backward-compat sensitive** (see rules). | +| `@clerk/ui` | ⚠️ The React components powering the hosted sign-in / sign-up UI. **Backward-compat sensitive**. | | `@clerk/mosaic` | Experimental next-generation components (`UserButton` today). Independently releasable; reads Clerk context from the host SDK. | | `@clerk/react` | Shared React hooks/context (`useAuth`, `useUser`, ...) consumed by the React-based adapters. | | `@clerk/nextjs` | Next.js SDK: middleware, route handlers, server components. | @@ -155,7 +155,7 @@ Each rule below restates `AGENTS.md`; the parenthetical is how it is enforced. `commit-msg` hook. Valid `scope` = any `packages/*` short name **and** its `clerk-`-stripped form (so `clerk-js` accepts `clerk-js` or `js`), plus `repo`, `release`, `e2e`, `ci`, `*`. `docs` is a valid **type**, not a scope. Source of truth: `commitlint.config.ts`. -- **`clerk-js` and `ui` must stay backwards-compatible across non-major releases.** A new `clerk-js` +- **`clerk-js` and `ui` must stay backward-compatible across non-major releases.** A new `clerk-js` runtime loads into apps still pinned to an _older_ framework SDK (`@clerk/nextjs`, etc.), so removing or renaming anything an older SDK calls breaks those apps in production. (`break-check` flags API-surface changes in `api-changes.yml`, but that check is informational; shipping such a diff --git a/.claude/skills/clerk-monorepo/references/breaking-changes.md b/.claude/skills/clerk-monorepo/references/breaking-changes.md index b0c7bb2652b..a698e24d916 100644 --- a/.claude/skills/clerk-monorepo/references/breaking-changes.md +++ b/.claude/skills/clerk-monorepo/references/breaking-changes.md @@ -57,7 +57,7 @@ If you need to ship something that is not yet stable: All three are described in `docs/CONTRIBUTING.md` (the "Experimental and internal APIs" section), which states the SemVer carve-out explicitly for the `/experimental` subpath; the `__internal_` / -`__experimental_` prefixes are conventions signalling the same no-guarantee expectation. All are +`__experimental_` prefixes are conventions signaling the same no-guarantee expectation. All are exempt from the breaking-change rules above. ## What CI enforces diff --git a/.claude/skills/clerk-monorepo/references/changesets.md b/.claude/skills/clerk-monorepo/references/changesets.md index 7496ef9c9b8..90d1dd00ea2 100644 --- a/.claude/skills/clerk-monorepo/references/changesets.md +++ b/.claude/skills/clerk-monorepo/references/changesets.md @@ -59,7 +59,7 @@ package. The whole file is exactly two delimiters and nothing else: Write it with the Write tool like any other file. If you must use the shell, `printf -- '---\n---\n' > .changeset/.md` produces the same bytes, but there is no reason to -verify it with `cat -A` or `od -c` afterwards. The format has no hidden characters. +verify it with `cat -A` or `od -c` afterward. The format has no hidden characters. ## Which packages to list diff --git a/.claude/skills/clerk-monorepo/references/package-map.md b/.claude/skills/clerk-monorepo/references/package-map.md index c5cabf3765b..74a3d9a040e 100644 --- a/.claude/skills/clerk-monorepo/references/package-map.md +++ b/.claude/skills/clerk-monorepo/references/package-map.md @@ -14,14 +14,14 @@ The 25 active, git-tracked packages, the dependency shape, and the full "change Categories: **foundational** (depended on by most others), **browser-runtime** (ships to the browser directly), **adapter** (framework SDK), **ui/i18n**, **tooling**. The "BC" column marks the two packages whose runtime is pushed into apps pinned to older SDKs, so they carry the strict -backwards-compatibility contract (see `breaking-changes.md`). +backward-compatibility contract (see `breaking-changes.md`). | Package | Category | BC | Purpose | | ----------------------------- | --------------- | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `@clerk/shared` | foundational | | Internal utilities used by all SDKs (storage, events, React helpers). Hosts the shared types as `@clerk/shared/types`. Most-depended-on package. | | `@clerk/backend` | foundational | | Backend API REST client, JWT verification, webhook helpers. Used by every server adapter. | -| `@clerk/clerk-js` | browser-runtime | ⚠️ | The browser runtime (script tag). Backwards-compat sensitive. | -| `@clerk/ui` | ui | ⚠️ | React components for the hosted sign-in / sign-up flows (`packages/ui/src/components`). Consumed by the react/astro/vue/chrome-extension adapters. Backwards-compat sensitive. | +| `@clerk/clerk-js` | browser-runtime | ⚠️ | The browser runtime (script tag). Backward-compat sensitive. | +| `@clerk/ui` | ui | ⚠️ | React components for the hosted sign-in / sign-up flows (`packages/ui/src/components`). Consumed by the react/astro/vue/chrome-extension adapters. Backward-compat sensitive. | | `@clerk/mosaic` | ui | | Experimental next-generation React components. Public ESM package (`UserButton` + `styles.css`). Ships unstyled, accessible primitives (dialog, menu, popover, ...) internally under `src/primitives/`. Reads Clerk context from the host SDK via `@clerk/shared`. | | `@clerk/react` | adapter (core) | | React hooks and context (`useAuth`, `useUser`, `useOrganization`, ...). Shared by the React-based adapters. | | `@clerk/nextjs` | adapter | | Next.js SDK: middleware, route handlers, server components. | @@ -82,7 +82,7 @@ error traces back to it, rebuild shared: `pnpm turbo build --filter=@clerk/share - **The hosted sign-in / sign-up UI (components, layout)**: `packages/ui/src/components` (`@clerk/ui`). Strings live in `packages/localizations/src`. `@clerk/ui` is consumed by the `react`/`astro`/`vue`/`chrome-extension` adapters; its compiled runtime is delivered alongside - `clerk-js` (both are backwards-compat sensitive), though `clerk-js` does not declare it as a package + `clerk-js` (both are backward-compat sensitive), though `clerk-js` does not declare it as a package dependency. Watch with `pnpm dev:fe-libs`. For the theming/appearance system specifically, read the repo-root `references/theming-architecture.md` (not this skill's own `references/` dir). - **Backend token verification / JWT / Backend API client**: `packages/backend/src` (JWT logic under diff --git a/.claude/skills/mosaic/SKILL.md b/.claude/skills/mosaic/SKILL.md index dd3fe28724d..42e8ad5c9c5 100644 --- a/.claude/skills/mosaic/SKILL.md +++ b/.claude/skills/mosaic/SKILL.md @@ -89,7 +89,7 @@ get wrong: produced it.** No "not a `size`, because…", no rejected alternatives, no history of what the prop used to be. A reader is there to learn what the thing does, and every sentence of rationale is a sentence they have to skim past to find it. -State the behaviour plainly and briefly, then stop. +State the behavior plainly and briefly, then stop. ```mdx diff --git a/.claude/skills/mosaic/references/machines.md b/.claude/skills/mosaic/references/machines.md index 00dafc70a09..d3bfe208100 100644 --- a/.claude/skills/mosaic/references/machines.md +++ b/.claude/skills/mosaic/references/machines.md @@ -10,7 +10,7 @@ feature — and the controller is the only thing that sends to it. A flow without those properties does not need one: `useState` in the controller is the right answer for a boolean that never touches async, and a controller can run a machine for its coordinated core with `useState` flags beside it. The view -cannot tell the difference either way. `machine/ADOPTION.md` is the judgement +cannot tell the difference either way. `machine/ADOPTION.md` is the judgment call, with real before/after migrations and an "honest boundary" table of what stays `useState`. diff --git a/.claude/skills/mosaic/references/migration.md b/.claude/skills/mosaic/references/migration.md index c4da87fe3f0..4523b86bc4a 100644 --- a/.claude/skills/mosaic/references/migration.md +++ b/.claude/skills/mosaic/references/migration.md @@ -110,5 +110,5 @@ not committed. Tests green, then a changeset and a conventional commit. See the `clerk-monorepo` skill for the dev loop and the hard rules. In short: `pnpm changeset` describing the user-facing change, scope `ui`, and remember non-major `packages/ui` changes -load into older SDKs in the wild, so keep the public surface backwards +load into older SDKs in the wild, so keep the public surface backward compatible. diff --git a/.claude/skills/mosaic/references/motion.md b/.claude/skills/mosaic/references/motion.md index 5485507327a..a0cf240f798 100644 --- a/.claude/skills/mosaic/references/motion.md +++ b/.claude/skills/mosaic/references/motion.md @@ -60,7 +60,7 @@ symmetric curve slows at both ends of each fade, keeping the reversal smooth. Keep the pulse duration on the component and disable the animation under `prefers-reduced-motion: reduce`. -## A curve has a direction — don't run the entrance curve backwards +## A curve has a direction — don't run the entrance curve backward The single most common motion bug in this codebase. `--cl-ease-default` is front-loaded and carries its endpoint ~2% past target before settling: a change @@ -128,7 +128,7 @@ collapsing a row". ## Asymmetry, in three places **Duration.** Exits are shorter than entrances — an arrival earns a moment to -settle, a dismissal is an acknowledgement. `--cl-duration-base` in, +settle, a dismissal is an acknowledgment. `--cl-duration-base` in, `--cl-duration-fast` out, a 1.5:1 ratio. Stay on the duration scale; a ratio that needs an off-scale value is not worth a new token for one component. @@ -341,7 +341,7 @@ color interpolation is already perceptually non-uniform, an ease on top just dra the midpoint, and `--cl-ease-default`'s overshoot would extrapolate past the target color. Reserve the curves for geometry. -**The arrival is always `0s`. Only the exit is a judgement call**, and what decides +**The arrival is always `0s`. Only the exit is a judgment call**, and what decides it is whether a pointer traverses the element on its way somewhere else: | the highlight sits on… | in | out | @@ -482,7 +482,7 @@ An isolated button never fails this visibly, but the arrival is the same rule ei way; there is no button-versus-row split on the way in. The same arithmetic sizes the comet trail: a 0.15s exit is a longer fade than the -0.1s modelled above, so it strings out proportionally more rows behind the pointer. +0.1s modeled above, so it strings out proportionally more rows behind the pointer. That is why the exit collapses to `0s` here even though it stays at 0.15s on a button. diff --git a/.claude/skills/mosaic/references/stylex.md b/.claude/skills/mosaic/references/stylex.md index ee881cc69ae..e622de29521 100644 --- a/.claude/skills/mosaic/references/stylex.md +++ b/.claude/skills/mosaic/references/stylex.md @@ -87,7 +87,7 @@ export const colorVars = stylex.defineVars(colorDefaults); - **DO** put light + dark in one token value via `light-dark()`. Never ship a second `@media (prefers-color-scheme: dark)` copy of a color. - **DO** reserve `--cl-*`-prefixed keys for the public, overridable contract. -- **DO** derive a gray-backed colour token from `--cl-color-neutral` mixed into +- **DO** derive a gray-backed color token from `--cl-color-neutral` mixed into `--cl-color-background` (`neutralMix(80, 98)` in `tokens.stylex.ts`) rather than restating an oklch value. There is no gray scale: retinting neutral retints the ramp. - **DO** name internal, non-contract vars with a `--_cl-*` prefix (e.g. a value a @@ -102,7 +102,7 @@ export const colorVars = stylex.defineVars(colorDefaults); `--cl-color-brand-hover-12`. - **DON'T** mint a per-step derivative token for something a `calc()`/`color-mix()` can express from an existing token. -- **DON'T** give one value two public names. The focus ring's colour is +- **DON'T** give one value two public names. The focus ring's color is `--cl-color-ring` and nothing else — a `--cl-focus-outline-color` alias beside it would let a consumer override one and not the other, and the ring's appearance would then depend on which they picked. @@ -298,7 +298,7 @@ a bare `:active` stays single — and the hover wins while the button is pressed } ``` -The symptom is a hovered button that never shows its pressed colour on a mouse +The symptom is a hovered button that never shows its pressed color on a mouse device, while touch devices look correct. - **DO** write the hover branch as `':hover:not(:active)'`. It stops the hover rule @@ -344,7 +344,7 @@ Worked example: `packages/mosaic/src/components/button/button.styles.ts`. `styles/focus-outline.styles.ts` into the element's `stylex.props(...)`: `focusOutline.visible` for the element's own keyboard focus, `focusOutline.within` for a **container** that should ring when a child is focused. The ring is one - decision for the whole system — width, style, colour and offset all come from + decision for the whole system — width, style, color and offset all come from `--cl-focus-outline-*` and `--cl-color-ring`, so a theme retargets every ring at once. @@ -362,7 +362,7 @@ Worked example: `packages/mosaic/src/components/button/button.styles.ts`. The ring is written as **longhands**, not the `outline` shorthand: StyleX ranks a longhand above a shorthand regardless of argument order, so it survives a component or consumer style carrying `outline: 'none'`, and a component can - re-colour its ring without restating the width and style. + re-color its ring without restating the width and style. `Input` is the deliberate exception — it marks focus with a border and box-shadow rather than a ring, and keeps an outline only under `forced-colors`. @@ -394,7 +394,7 @@ Worked example: `packages/mosaic/src/components/button/button.styles.ts`. transform at `--cl-duration-fast` still wants the curve. - **DON'T** reuse `--cl-ease-default` for something **leaving**. It is an arrival - curve; run backwards it stalls for most of its duration and its overshoot + curve; run backward it stalls for most of its duration and its overshoot becomes a wobble past the target. Departures take `easingVars['--cl-ease-exit']` at a shorter duration. See `motion.md` — enter/exit asymmetry has its own reference, with the measurements behind these rules. @@ -447,7 +447,7 @@ triggered by unrelated ancestors: // checkbox.markers.stylex.ts — a define-primitive module, its own file export const checkboxScope = stylex.defineMarker(); -// checkbox.styles.ts — the inner mark reacts to the labelled wrapper's state +// checkbox.styles.ts — the inner mark reacts to the labeled wrapper's state borderColor: { default: colorVars['--cl-color-border'], [stylex.when.ancestor(':hover', checkboxScope)]: { diff --git a/.claude/skills/mosaic/references/views.md b/.claude/skills/mosaic/references/views.md index 8591cb6c450..de4569f7088 100644 --- a/.claude/skills/mosaic/references/views.md +++ b/.claude/skills/mosaic/references/views.md @@ -105,7 +105,7 @@ const removed = useRef(undefined); const removeRow = async (row: Row) => { const index = rows.findIndex(candidate => candidate.id === row.id); await onRemove(row.id); - // Only once it is really gone: a cancelled or failed attempt keeps its own trigger. + // Only once it is really gone: a canceled or failed attempt keeps its own trigger. removed.current = index; }; @@ -124,7 +124,7 @@ Prefer the row that took the removed one's place, the last row when it was the last, and a control that outlives the list once it is empty. Test the removal, not just the cancel: `toHaveFocus()` on the row that should -have caught it. A suite that only asserts focus after cancelling passes while +have caught it. A suite that only asserts focus after canceling passes while every successful removal drops focus on the floor. ## Testing diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38fcec32813..638300b8181 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,6 +85,9 @@ jobs: - name: Check Dashboard links run: pnpm lint:dashboard-links + - name: Check American spelling + run: pnpm lint:american-spelling + - name: Verify localizations are generated run: | pnpm --filter @clerk/localizations generate @@ -580,6 +583,7 @@ jobs: VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }} - name: Sanitize artifact name + # american-spelling-ignore-next-line: cancelled -- GitHub Actions status function if: ${{ cancelled() || failure() }} id: sanitize run: | @@ -589,6 +593,7 @@ jobs: TEST_NAME: ${{ matrix.test-name }} - name: Upload test-results + # american-spelling-ignore-next-line: cancelled -- GitHub Actions status function if: ${{ cancelled() || failure() }} uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: diff --git a/.github/workflows/e2e-staging.yml b/.github/workflows/e2e-staging.yml index fb421178e39..16fb05dcce7 100644 --- a/.github/workflows/e2e-staging.yml +++ b/.github/workflows/e2e-staging.yml @@ -36,7 +36,7 @@ concurrency: # Key on the clerk_go commit being validated rather than the (effectively always "main") # ref, so distinct staging deploys no longer cancel each other and each commit can report # its own result. Duplicate dispatches for the SAME commit still de-dupe; manual dispatches - # without a commit SHA fall back to the unique run_id and are never cancelled. + # without a commit SHA fall back to the unique run_id and are never canceled. group: ${{ github.workflow }}-${{ github.event.inputs.clerk-go-commit-sha || github.event.client_payload.clerk-go-commit-sha || github.run_id }} cancel-in-progress: true @@ -305,6 +305,7 @@ jobs: NODE_EXTRA_CA_CERTS: ${{ github.workspace }}/integration/certs/rootCA.pem - name: Upload test-results + # american-spelling-ignore-next-line: cancelled -- GitHub Actions status function if: ${{ cancelled() || failure() }} uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: @@ -319,6 +320,7 @@ jobs: # integration/playwright-report/. Keep that prefix here. `warn` (not `ignore`) # so a future path regression surfaces in the logs instead of failing silently. - name: Upload Playwright JSON report + # american-spelling-ignore-next-line: cancelled -- GitHub Actions status function if: ${{ !cancelled() }} uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 54eef60b355..cd57ad5c516 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -261,6 +261,7 @@ jobs: - name: Generate notification payload id: notification + # american-spelling-ignore-next-line: cancelled -- GitHub Actions status function if: ${{ !cancelled() && steps.changesets.outputs.published == 'true' }} env: PUBLISHED_PACKAGES: ${{ steps.changesets.outputs.publishedPackages }} @@ -275,6 +276,7 @@ jobs: - name: Send commit log to Slack id: slack + # american-spelling-ignore-next-line: cancelled -- GitHub Actions status function if: ${{ !cancelled() && steps.changesets.outputs.published == 'true' && steps.notification.outcome == 'success' }} uses: slackapi/slack-github-action@fcfb566f8b0aab22203f066d80ca1d7e4b5d05b3 # v1.27.1 with: diff --git a/.typedoc/README.md b/.typedoc/README.md index 68ddcc3a807..3f98c9a9697 100644 --- a/.typedoc/README.md +++ b/.typedoc/README.md @@ -75,9 +75,9 @@ The `.typedoc/docs` folder is inside the `.gitignore` on purpose. Its contents w ### Adding a package to the Typedoc output -Make sure that the package directory is listed inside [`typedoc.config.mjs`](../typedoc.config.mjs) `config.entryPoints`. Afterwards Typedoc will inspect the `exports` map and `main` key inside `package.json` to determine the entrypoints for the package. +Make sure that the package directory is listed inside [`typedoc.config.mjs`](../typedoc.config.mjs) `config.entryPoints`. Afterward Typedoc will inspect the `exports` map and `main` key inside `package.json` to determine the entrypoints for the package. -If for some reason this doesn't work, spend time investigating it. If afterwards it still doesn't work, you can add a `typedoc.json` file to the package and define the `entryPoints` there. +If for some reason this doesn't work, spend time investigating it. If afterward it still doesn't work, you can add a `typedoc.json` file to the package and define the `entryPoints` there. > [!IMPORTANT] > If you're generating documentation for files/APIs that are not exported/accessible to a user, it's an error. Unless you want to limit the entry points (e.g. a package exports internal functionality) or modify things like the `compilerOptions`, you probably should not define a custom `typedoc.json` file inside a package. diff --git a/AGENTS.md b/AGENTS.md index 8e14f6f2746..1eef72e66d1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ Clerk's JavaScript SDK and library monorepo. ## Rules -- Non-major releases in `packages/clerk-js` and `packages/ui` are pushed out to consuming applications without requiring explicit package updates. This means a new `clerk-js` runtime can load into an app pinned to an older `@clerk/nextjs` (or any other framework SDK) version, so changes must remain backwards-compatible with SDK versions already in the wild, not just the current monorepo state. Removing or renaming anything an older SDK still calls will break production for those users. Extra care must be put into any changes to these packages. +- Non-major releases in `packages/clerk-js` and `packages/ui` are pushed out to consuming applications without requiring explicit package updates. This means a new `clerk-js` runtime can load into an app pinned to an older `@clerk/nextjs` (or any other framework SDK) version, so changes must remain backward-compatible with SDK versions already in the wild, not just the current monorepo state. Removing or renaming anything an older SDK still calls will break production for those users. Extra care must be put into any changes to these packages. - The API exposed from the core Clerk class in `packages/clerk-js/src/core/clerk.ts` is a contract that is depended on by internal and external consumers (including older SDK versions still loading the latest `clerk-js`). Changes to this API must be done in a major version to avoid breakage. - Unless specifically instructed to do so by the user, _do not write any code comments_. The only permissible time to edit existing comments is when the behavior they describe has been modified in such a way that the comment is no longer accurate. When another rule instructs you to write a JSDoc comment, specifically call out the content of the comment to the user for review. - Use `pnpm` only. `npm` and `yarn` are blocked by `preinstall`. Node `>=24.15`, pnpm `>=10.33`. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 9fae4c95a29..d01046fd2cc 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -96,7 +96,7 @@ If you want to run the `dev` script of an individual package, navigate to the fo The repo ships configuration for AI coding agents, picked up automatically from the checkout with no setup: -- [`AGENTS.md`](../AGENTS.md) holds the hard rules (package manager, changesets, commit conventions, backwards compatibility) and is read by most agents. +- [`AGENTS.md`](../AGENTS.md) holds the hard rules (package manager, changesets, commit conventions, backward compatibility) and is read by most agents. - Claude Code also loads the `clerk-monorepo` skill from [`.claude/skills/`](../.claude/skills/README.md). It activates on its own for monorepo questions, or explicitly via `/clerk-monorepo`. The README in that directory explains how skills work and how to maintain them. - Cursor reads the rules in `.cursor/rules/`. @@ -104,6 +104,8 @@ The repo ships configuration for AI coding agents, picked up automatically from Updating documentation is an important part of the contribution process. If you are changing existing behavior or adding a new feature, make sure [Clerk's documentation](https://github.com/clerk/clerk-docs) is also updated. +Write in American English (`color`, `canceled`, `toward`), in code as well as prose. `pnpm lint:american-spelling` flags British spellings in CI, and `pnpm lint:american-spelling --fix` rewrites them. To keep a direct quotation or a name you don't own, put an `american-spelling-ignore-next-line` comment on the line above it that lists the words to allow after a colon, with an optional `--` reason. + To improve the in-editor experience when using Clerk's SDKs, we do our best to add [JSDoc comments](https://jsdoc.app/about-getting-started.html) to our package's public exports. The JSDoc comments should not attempt to duplicate any existing type information, but should provide meaningful additional context or references. If you are adding a new export, make sure it has a JSDoc comment. If you are updating an existing export, make sure any existing comments are updated appropriately. There are some styleguide decisions you should follow when authoring JSDoc comments: @@ -190,7 +192,7 @@ As explained in [documenting your changes](#documenting-your-changes), we use JS For a comprehensive guide on **authoring** JSDoc/Typedoc comments, see [this guide](https://www.notion.so/clerkdev/Typedoc-JSDoc-1df2b9ab44fe808a8cf2c9cca324ea89?source=copy_link). -To review your changes locally, you can run `pnpm run typedoc:generate` to generate the docs. Afterwards, you can inspect the MDX files inside `.typedoc/docs`. But if you want to preview how the Typedoc output will look in Clerk Docs, there's a few things you need to do first: +To review your changes locally, you can run `pnpm run typedoc:generate` to generate the docs. Afterward, you can inspect the MDX files inside `.typedoc/docs`. But if you want to preview how the Typedoc output will look in Clerk Docs, there's a few things you need to do first: Create a PR that includes your changes to any Typedoc comments. Once the PR has been merged and a release is published, the release workflow automatically opens a PR in Clerk's docs repository with the Typedoc changes. diff --git a/docs/PUBLISH.md b/docs/PUBLISH.md index 3e03ba513f4..0b3f17337db 100644 --- a/docs/PUBLISH.md +++ b/docs/PUBLISH.md @@ -50,7 +50,7 @@ Notes: If a PR got merged into `main` that should also be released in older versions (e.g. critical security fixes), you'll need to backport said PR. You can do this by using the [`backport` script](https://github.com/clerk/javascript/blob/main/scripts/backport.mjs) inside `scripts`. -Duplicate the `.env.example` file inside `scripts` and rename it to `.env`. Fill out the `GITHUB_ACCESS_TOKEN` variable. Afterwards, you'll be able to run the CLI like so: +Duplicate the `.env.example` file inside `scripts` and rename it to `.env`. Fill out the `GITHUB_ACCESS_TOKEN` variable. Afterward, you'll be able to run the CLI like so: ```shell node backport.mjs release/v4 1234 diff --git a/integration/README.md b/integration/README.md index 507e2a54b7c..1e373838b60 100644 --- a/integration/README.md +++ b/integration/README.md @@ -369,7 +369,7 @@ Sometimes tests are passing locally but not in CI 😢 But there are ways to dig 1. Open the [`ci.yml`](../.github/workflows/ci.yml) file 1. Inside the **Setup** step (of the `integration-tests` job), add `verbose: true` to the arguments. This will enable more verbose logging from Turborepo and ensure that all logs are flushed the moment they appear. -1. Playwright will record traces of failed tests and they will be uploaded when the E2E step fails or is cancelled. Click on the **Upload test-results** step and download the archive. It contains Playwright traces. +1. Playwright will record traces of failed tests and they will be uploaded when the E2E step fails or is canceled. Click on the **Upload test-results** step and download the archive. It contains Playwright traces. 1. Open https://trace.playwright.dev/ and open your trace file If these information are not enough, it might be helpful to have a look at the temporary site that was created inside the test run. You'll need to make some adjustments: @@ -388,6 +388,7 @@ If these information are not enough, it might be helpful to have a look at the t ```yaml - name: Upload app artifacts + # american-spelling-ignore-next-line: cancelled -- GitHub Actions status function if: ${{ cancelled() || failure() }} uses: actions/upload-artifact@v4 with: diff --git a/integration/tests/reverification.test.ts b/integration/tests/reverification.test.ts index c00f43e6c92..59311c9dd50 100644 --- a/integration/tests/reverification.test.ts +++ b/integration/tests/reverification.test.ts @@ -128,7 +128,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withReverification] })( await expect(u.page.locator('.cl-profileSectionItem__phoneNumbers')).toContainText(formatedPhoneNumber); }); - test('reverification prompt can be cancelled when adding email', async ({ page, context }) => { + test('reverification prompt can be canceled when adding email', async ({ page, context }) => { const u = createTestUtils({ app, page, context }); await u.po.signIn.goTo(); await u.po.signIn.waitForMounted(); diff --git a/integration/tests/session-tasks-multi-session.test.ts b/integration/tests/session-tasks-multi-session.test.ts index 3af0aa2c58c..36e4086ea18 100644 --- a/integration/tests/session-tasks-multi-session.test.ts +++ b/integration/tests/session-tasks-multi-session.test.ts @@ -59,7 +59,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withSessionTasks] })( await u.page.waitForAppUrl('/'); // Create second user, to initiate a pending session - // Don't resolve task and switch to active session afterwards + // Don't resolve task and switch to active session afterward await u.po.signIn.goTo(); await u.po.signIn.setIdentifier(user2.email); await u.po.signIn.continue(); diff --git a/package.json b/package.json index c7c706f5e61..a7126ecea40 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "preinstall": "npx only-allow pnpm", "integration:secrets": "node ./scripts/1password-keys.mjs", "lint": "node ./scripts/lint.mjs", + "lint:american-spelling": "node ./scripts/american-spelling.mjs", "lint:attw": "FORCE_COLOR=1 turbo lint:attw", "lint:dashboard-links": "node ./scripts/check-dashboard-links.mjs", "lint:fix": "FORCE_COLOR=1 turbo lint -- --fix", diff --git a/packages/backend/src/tokens/request.ts b/packages/backend/src/tokens/request.ts index 07f16f0a97e..423d27dffc9 100644 --- a/packages/backend/src/tokens/request.ts +++ b/packages/backend/src/tokens/request.ts @@ -152,7 +152,7 @@ export const authenticateRequest: AuthenticateRequest = (async ( ): Promise | UnauthenticatedState> => { const authenticateContext = await createAuthenticateContext(createClerkRequest(request), options); - // Default tokenType is session_token for backwards compatibility. + // Default tokenType is session_token for backward compatibility. const acceptsToken = options.acceptsToken ?? TokenType.SessionToken; // machine-to-machine tokens can accept a machine secret or a secret key diff --git a/packages/clerk-js/src/core/__tests__/clerk.test.ts b/packages/clerk-js/src/core/__tests__/clerk.test.ts index de137e89533..764f240e79e 100644 --- a/packages/clerk-js/src/core/__tests__/clerk.test.ts +++ b/packages/clerk-js/src/core/__tests__/clerk.test.ts @@ -2149,7 +2149,7 @@ describe('Clerk singleton', () => { ); }); - it('still honours transferable: false', async () => { + it('still honors transferable: false', async () => { loadEnvironment(); mockClientFetch.mockReturnValue( Promise.resolve({ @@ -4157,7 +4157,7 @@ describe('Clerk singleton', () => { expect(mockClerkUICtor).toHaveBeenCalled(); }); - it('supports legacy clerkUICtor option for backwards compatibility', async () => { + it('supports legacy clerkUICtor option for backward compatibility', async () => { const mockClerkUIInstance = { mount: vi.fn() }; const mockClerkUICtor = vi.fn(function () { return mockClerkUIInstance; @@ -4172,7 +4172,7 @@ describe('Clerk singleton', () => { expect(mockClerkUICtor).toHaveBeenCalled(); }); - it('supports legacy clerkUiCtor option for backwards compatibility', async () => { + it('supports legacy clerkUiCtor option for backward compatibility', async () => { const mockClerkUIInstance = { mount: vi.fn() }; const mockClerkUICtor = vi.fn(function () { return mockClerkUIInstance; diff --git a/packages/clerk-js/src/core/__tests__/protect.test.ts b/packages/clerk-js/src/core/__tests__/protect.test.ts index 683c4420373..40b64b57f27 100644 --- a/packages/clerk-js/src/core/__tests__/protect.test.ts +++ b/packages/clerk-js/src/core/__tests__/protect.test.ts @@ -81,7 +81,7 @@ describe('Protect.load', () => { expect(localStorage.getItem('__clerk_protect_pid')).toBeNull(); }); - it('substitutes the placeholders it recognises and leaves the rest verbatim', async () => { + it('substitutes the placeholders it recognizes and leaves the rest verbatim', async () => { const protect = new Protect(); protect.load( environment([ diff --git a/packages/clerk-js/src/core/__tests__/protectSession.test.ts b/packages/clerk-js/src/core/__tests__/protectSession.test.ts index bb8056d59e9..55ccdb6fefa 100644 --- a/packages/clerk-js/src/core/__tests__/protectSession.test.ts +++ b/packages/clerk-js/src/core/__tests__/protectSession.test.ts @@ -152,7 +152,7 @@ describe('interpolatePlaceholders', () => { ).toBe('1.2.3/c/p/r'); }); - it('leaves an unrecognised placeholder verbatim', () => { + it('leaves an unrecognized placeholder verbatim', () => { // `{instance_id}` is not in the set and must not be: the instance id is the server's to place // into the config it serves, never something the client interpolates. expect(interpolatePlaceholders('{cid}/{nope}/{PID}/{instance_id}', { cid: 'c' })).toBe( @@ -160,7 +160,7 @@ describe('interpolatePlaceholders', () => { ); }); - it('leaves a recognised placeholder verbatim when there is no value for it', () => { + it('leaves a recognized placeholder verbatim when there is no value for it', () => { expect(interpolatePlaceholders('{cid}/{sdkver}', { cid: 'c' })).toBe('c/{sdkver}'); }); }); @@ -456,7 +456,7 @@ describe('ProtectSession inline token', () => { await expect(created?.getRequestParams()).resolves.toMatchObject({ __clerk_protect_token: 'v1.payload.mac' }); }); - it('ignores an entry stamped in the future, which means the clock moved backwards', () => { + it('ignores an entry stamped in the future, which means the clock moved backward', () => { localStorage.setItem('__clerk_protect_st', storedEntry({ at: Date.now() + 60 * 60 * 1_000 })); const { session: created } = session([loader()]); @@ -664,7 +664,7 @@ describe('ProtectSession storage', () => { // A second session on the same origin reads the same store rather than running its own loader. // The token names the instance that minted it and is verified server-side, so an origin serving - // two instances costs a rejected token — never a token honoured for the wrong instance. + // two instances costs a rejected token — never a token honored for the wrong instance. const b = session([loader()]); expect(b.session?.hasFreshToken()).toBe(true); expect(localStorage.getItem('__clerk_protect_st')).not.toBeNull(); @@ -672,7 +672,7 @@ describe('ProtectSession storage', () => { }); describe('ProtectSession deadlines', () => { - it('honours a deadline that arrived under its wire name', async () => { + it('honors a deadline that arrived under its wire name', async () => { // The loaders array is assigned straight out of /v1/environment with no case conversion, so a // fixture written as a TS literal agrees with the type by construction and can never catch a // key the server does not send. `token_timeout_ms` was read as `tokenTimeoutMs` and was @@ -743,11 +743,11 @@ describe('ProtectSession deadlines', () => { describe('ProtectSession cross-tab single flight', () => { /** - * A serialising Web Locks stand-in. jsdom has no `navigator.locks`, and the `browser-tabs-lock` + * A serializing Web Locks stand-in. jsdom has no `navigator.locks`, and the `browser-tabs-lock` * fallback is globally mocked to always grant, so without this the double-check inside the lock * would never be exercised. */ - const installSerialisingLocks = () => { + const installSerializingLocks = () => { let tail: Promise = Promise.resolve(); const request = vi.fn((_key: string, _options: unknown, callback: () => Promise) => { const result = tail.then(() => callback()); @@ -763,7 +763,7 @@ describe('ProtectSession cross-tab single flight', () => { }); it('runs the loader once when several tabs start together', async () => { - const request = installSerialisingLocks(); + const request = installSerializingLocks(); const a = session([loader({ token_timeout_ms: 1_000 })]); const b = session([loader({ token_timeout_ms: 1_000 })]); @@ -784,7 +784,7 @@ describe('ProtectSession cross-tab single flight', () => { it('uses whatever a leader wrote when the lock is never granted', async () => { Object.defineProperty(navigator, 'locks', { value: { - // Mirrors SafeLock's behaviour when its own AbortSignal fires: the callback never runs. + // Mirrors SafeLock's behavior when its own AbortSignal fires: the callback never runs. request: vi.fn(() => Promise.reject(new DOMException('aborted', 'AbortError'))), }, configurable: true, diff --git a/packages/clerk-js/src/core/__tests__/tokenCache.test.ts b/packages/clerk-js/src/core/__tests__/tokenCache.test.ts index 27c0a458401..372478de654 100644 --- a/packages/clerk-js/src/core/__tests__/tokenCache.test.ts +++ b/packages/clerk-js/src/core/__tests__/tokenCache.test.ts @@ -1605,7 +1605,7 @@ describe('SessionTokenCache', () => { // Advance past refresh fire time vi.advanceTimersByTime(44 * 1000); - // Only the second (background refresh) callback should fire; the hydrate timer was cancelled + // Only the second (background refresh) callback should fire; the hydrate timer was canceled expect(hydrateRefresh).not.toHaveBeenCalled(); expect(backgroundRefresh).toHaveBeenCalledTimes(1); }); @@ -1623,7 +1623,7 @@ describe('SessionTokenCache', () => { SessionTokenCache.set({ ...key, tokenResolver: Promise.resolve(token1) }); await Promise.resolve(); - // Second set() with 120s TTL — old 30s expiration timer should be cancelled + // Second set() with 120s TTL — old 30s expiration timer should be canceled SessionTokenCache.set({ ...key, tokenResolver: Promise.resolve(token2) }); await Promise.resolve(); @@ -1788,7 +1788,7 @@ describe('SessionTokenCache', () => { vi.advanceTimersByTime(44 * 1000); - // The old onRefresh should have been cancelled, and no new one was scheduled + // The old onRefresh should have been canceled, and no new one was scheduled expect(onRefresh).not.toHaveBeenCalled(); }); }); diff --git a/packages/clerk-js/src/core/protect.ts b/packages/clerk-js/src/core/protect.ts index 446f0b5e7a7..5e36630b631 100644 --- a/packages/clerk-js/src/core/protect.ts +++ b/packages/clerk-js/src/core/protect.ts @@ -18,7 +18,7 @@ export class Protect { * Resolved from the APPLIED loaders rather than from the config, because rollout is decided by * a random draw per page load: a loader being ramped can carry its own value without changing * anything for browsers still on the loader it replaces, and which one a browser got cannot be - * recomputed afterwards. + * recomputed afterward. */ get challengeLoadTimeoutMs(): number | undefined { return this.#challengeLoadTimeoutMs; diff --git a/packages/clerk-js/src/core/protectSession.ts b/packages/clerk-js/src/core/protectSession.ts index ee36a82b0e9..6d07764162b 100644 --- a/packages/clerk-js/src/core/protectSession.ts +++ b/packages/clerk-js/src/core/protectSession.ts @@ -132,8 +132,8 @@ type MintedToken = Omit; type LoaderOutcome = 'loaded' | 'error' | 'timeout'; /** - * Substitutes the closed placeholder set. A recognised placeholder with no value available, and - * any unrecognised `{…}`, is left verbatim — the server treats an unsubstituted placeholder as + * Substitutes the closed placeholder set. A recognized placeholder with no value available, and + * any unrecognized `{…}`, is left verbatim — the server treats an unsubstituted placeholder as * "no id" and serves normally, which is what keeps older SDK builds working against a templated * loader config. */ @@ -292,7 +292,7 @@ function normalizeFloor(value: unknown): number { * running slow keeps an entry alive long past its real expiry and a clock running fast throws * good ones away. Elapsed time since we stored it is measured on one clock, so a constant offset * cancels: an entry older than any token we would ever be issued is stale whatever `exp` claims, - * and one stamped in the future means the clock moved backwards under us. + * and one stamped in the future means the clock moved backward under us. * * This bounds the damage rather than removing it — a fully skew-proof check needs the server to * send a relative lifetime instead of an absolute `exp`, which is a wire change. diff --git a/packages/clerk-js/src/core/resources/OrganizationDomain.ts b/packages/clerk-js/src/core/resources/OrganizationDomain.ts index 12256963e51..09637297827 100644 --- a/packages/clerk-js/src/core/resources/OrganizationDomain.ts +++ b/packages/clerk-js/src/core/resources/OrganizationDomain.ts @@ -98,7 +98,7 @@ export class OrganizationDomain extends BaseResource implements OrganizationDoma expiresAt: unixEpochToDate(affiliationVerificationJSON.expires_at), }; this.affiliationVerification = affiliationVerification; - // Deprecated alias, kept in sync for backwards compatibility. + // Deprecated alias, kept in sync for backward compatibility. this.verification = affiliationVerification; } else { this.affiliationVerification = null; diff --git a/packages/clerk-js/src/core/resources/SignIn.ts b/packages/clerk-js/src/core/resources/SignIn.ts index 94a4916c196..4bb13abb5f2 100644 --- a/packages/clerk-js/src/core/resources/SignIn.ts +++ b/packages/clerk-js/src/core/resources/SignIn.ts @@ -426,7 +426,7 @@ export class SignIn extends BaseResource implements SignInResource { // A pending challenge with nowhere to navigate to. Throw rather than return, so the method still // either navigates or throws: a caller that doesn't handle challenges gets an error it can - // recognise instead of a silent success. A caller that does runs the challenge and calls back + // recognize instead of a silent success. A caller that does runs the challenge and calls back // in with `continueSignIn`. const throwChallengeRequired = (): never => { throw new ClerkRuntimeError('A verification challenge must be completed before this sign-in can continue.', { diff --git a/packages/clerk-js/src/core/resources/__tests__/Organization.test.ts b/packages/clerk-js/src/core/resources/__tests__/Organization.test.ts index 34867fb44dc..4aba3033757 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Organization.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Organization.test.ts @@ -224,7 +224,7 @@ describe('Organization', () => { // @ts-ignore const callBody = BaseResource._fetch.mock.calls[0][0].body; - // Backwards-compatible: an omitted (or empty) `domains` is not sent. + // Backward-compatible: an omitted (or empty) `domains` is not sent. expect('domains' in callBody).toBe(false); }); diff --git a/packages/clerk-js/src/core/resources/__tests__/Passkey.test.ts b/packages/clerk-js/src/core/resources/__tests__/Passkey.test.ts index 37b3cb43a63..4d6454c2b49 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Passkey.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Passkey.test.ts @@ -32,7 +32,7 @@ const passkeyJSON = (authenticatorAttachment?: 'platform' | 'cross-platform') => }, }) as unknown as PasskeyJSON; -const cancelledError = new ClerkWebAuthnError('Passkey registration was cancelled or timed out.', { +const canceledError = new ClerkWebAuthnError('Passkey registration was canceled or timed out.', { code: 'passkey_registration_cancelled', }); @@ -66,12 +66,12 @@ describe('Passkey', () => { }); describe('registerPasskey', () => { - it('deletes the pending passkey when the browser prompt is cancelled', async () => { - setupClerk(() => Promise.resolve({ publicKeyCredential: null, error: cancelledError })); + it('deletes the pending passkey when the browser prompt is canceled', async () => { + setupClerk(() => Promise.resolve({ publicKeyCredential: null, error: canceledError })); const fetchMock = vi.fn().mockResolvedValue({ response: passkeyJSON() }); (BaseResource as any)._fetch = fetchMock; - await expect(Passkey.registerPasskey()).rejects.toBe(cancelledError); + await expect(Passkey.registerPasskey()).rejects.toBe(canceledError); expect(fetchMock).toHaveBeenCalledWith(deleteCall); expect(fetchMock).not.toHaveBeenCalledWith( @@ -80,7 +80,7 @@ describe('Passkey', () => { }); it('surfaces the original error when the cleanup itself fails', async () => { - setupClerk(() => Promise.resolve({ publicKeyCredential: null, error: cancelledError })); + setupClerk(() => Promise.resolve({ publicKeyCredential: null, error: canceledError })); const fetchMock = vi.fn().mockImplementation(({ method }) => { if (method === 'DELETE') { return Promise.reject(new Error('session_reverification_required')); @@ -89,7 +89,7 @@ describe('Passkey', () => { }); (BaseResource as any)._fetch = fetchMock; - await expect(Passkey.registerPasskey()).rejects.toBe(cancelledError); + await expect(Passkey.registerPasskey()).rejects.toBe(canceledError); }); it('deletes the pending passkey when the device lacks a platform authenticator', async () => { @@ -105,7 +105,7 @@ describe('Passkey', () => { }); it('does not attempt a delete when the created passkey has no id', async () => { - setupClerk(() => Promise.resolve({ publicKeyCredential: null, error: cancelledError })); + setupClerk(() => Promise.resolve({ publicKeyCredential: null, error: canceledError })); const fetchMock = vi.fn().mockResolvedValue(null); (BaseResource as any)._fetch = fetchMock; diff --git a/packages/clerk-js/src/core/resources/__tests__/SignIn.test.ts b/packages/clerk-js/src/core/resources/__tests__/SignIn.test.ts index 7942dfcc7ac..8beedb51d86 100644 --- a/packages/clerk-js/src/core/resources/__tests__/SignIn.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/SignIn.test.ts @@ -2371,7 +2371,7 @@ describe('SignIn', () => { const mockIsWebAuthnSupported = vi.fn().mockReturnValue(true); const mockWebAuthnGetCredential = vi.fn().mockResolvedValue({ publicKeyCredential: null, - error: new Error('User cancelled'), + error: new Error('User canceled'), }); SignIn.clerk = { diff --git a/packages/clerk-js/src/core/tokenStore.ts b/packages/clerk-js/src/core/tokenStore.ts index 523141a068c..cab1e36d4c6 100644 --- a/packages/clerk-js/src/core/tokenStore.ts +++ b/packages/clerk-js/src/core/tokenStore.ts @@ -3,7 +3,7 @@ * * Pure storage: no timers, no BroadcastChannel, and no JWT knowledge. The cache * layers proactive-refresh scheduling and cross-tab synchronization on top. - * Synchronous by design — the in-memory path never needs to be async — modelled + * Synchronous by design — the in-memory path never needs to be async — modeled * on auth0-spa-js's synchronous cache interface. */ export interface TokenStore { diff --git a/packages/clerk-js/src/utils/__tests__/authenticateWithTransport.test.ts b/packages/clerk-js/src/utils/__tests__/authenticateWithTransport.test.ts index c00b7580977..19ea3723e83 100644 --- a/packages/clerk-js/src/utils/__tests__/authenticateWithTransport.test.ts +++ b/packages/clerk-js/src/utils/__tests__/authenticateWithTransport.test.ts @@ -232,7 +232,7 @@ describe('_authenticateWithTransport', () => { const clerk = makeClerk(); const transport = { getRedirectUrl: vi.fn().mockResolvedValue('myapp://sso-callback'), - open: vi.fn().mockRejectedValue(new Error('cancelled')), + open: vi.fn().mockRejectedValue(new Error('canceled')), }; const resource = { reload: vi.fn() } as any; const authenticateMethod = vi.fn(async (_params, navigate) => navigate('https://provider.example/auth')); @@ -246,7 +246,7 @@ describe('_authenticateWithTransport', () => { params: {} as any, callbackParams: {}, }), - ).rejects.toThrow('cancelled'); + ).rejects.toThrow('canceled'); expect(clerk.__internal_handleResourceCallback).not.toHaveBeenCalled(); }); diff --git a/packages/clerk-js/src/utils/enterpriseConnection.ts b/packages/clerk-js/src/utils/enterpriseConnection.ts index c3e8f8deb7b..7a629c51683 100644 --- a/packages/clerk-js/src/utils/enterpriseConnection.ts +++ b/packages/clerk-js/src/utils/enterpriseConnection.ts @@ -41,7 +41,7 @@ export function toEnterpriseConnectionBody( // `domains` is an array of FQDN strings. The form serializer downstream emits // each element as a repeated `domains` form field (e.g. `domains=a.com&domains=b.com`), // matching how the backend parses repeated form params. An omitted or empty - // array is not sent, keeping older callers backwards-compatible. + // array is not sent, keeping older callers backward-compatible. const domains = (params as CreateOrganizationEnterpriseConnectionParams).domains; if (domains && domains.length > 0) { body.domains = domains; diff --git a/packages/clerk-js/src/utils/mergePatch.ts b/packages/clerk-js/src/utils/mergePatch.ts index 98fab5ea2a6..4c52c168307 100644 --- a/packages/clerk-js/src/utils/mergePatch.ts +++ b/packages/clerk-js/src/utils/mergePatch.ts @@ -10,7 +10,7 @@ import { dequal } from 'dequal'; * state, and we send the diff that makes the server side end up at the * desired state. * - * Behaviour: + * Behavior: * - both plain objects: recurse; emit only keys whose value changes * - `desired === null`: returned verbatim (caller decides what null means) * - any other type mismatch: `desired` is returned (full replace at that node) diff --git a/packages/electron-passkeys/README.md b/packages/electron-passkeys/README.md index 8bedc891f9b..fe2f497d0bb 100644 --- a/packages/electron-passkeys/README.md +++ b/packages/electron-passkeys/README.md @@ -24,6 +24,8 @@ createCredential(windowHandle: Buffer, optionsJson: string): Promise; getCredential(windowHandle: Buffer, optionsJson: string): Promise; ``` + + `createCredential`/`getCredential` take the window handle from `BrowserWindow#getNativeWindowHandle()` (to anchor the OS dialog) and JSON-encoded WebAuthn options with base64url binary fields. They always resolve with a JSON envelope — `{ ok: true, credential }` or `{ ok: false, error: { code, message } }` with `code` one of `cancelled | invalid_rp | not_supported | timeout | unknown` — and never reject for ceremony failures. ## Building from source diff --git a/packages/electron/src/main/__tests__/passkey-handlers.test.ts b/packages/electron/src/main/__tests__/passkey-handlers.test.ts index 2852a05d8b2..6a42e2e9add 100644 --- a/packages/electron/src/main/__tests__/passkey-handlers.test.ts +++ b/packages/electron/src/main/__tests__/passkey-handlers.test.ts @@ -79,12 +79,14 @@ describe('setupPasskeysMain', () => { it('relays a native error envelope from get', async () => { native.getCredential.mockResolvedValue( + // american-spelling-ignore-next-line: cancelled -- code the @clerk/electron-passkeys native addon emits JSON.stringify({ ok: false, error: { code: 'cancelled', message: 'user cancelled' } }), ); setupPasskeysMain(); const result = await getHandler(PASSKEY_CHANNELS.get)(event, { challenge: 'abc', rpId: 'example.com' }); + // american-spelling-ignore-next-line: cancelled -- code the @clerk/electron-passkeys native addon emits expect(result).toEqual({ ok: false, error: { code: 'cancelled', message: 'user cancelled' } }); }); diff --git a/packages/electron/src/main/oauth-transport.ts b/packages/electron/src/main/oauth-transport.ts index 763636a87de..f1531306a64 100644 --- a/packages/electron/src/main/oauth-transport.ts +++ b/packages/electron/src/main/oauth-transport.ts @@ -150,7 +150,7 @@ export function setupOAuthTransportIpcHandlers(options: OAuthTransportOptions): }); return () => { - disposePendingOAuthFlow(new Error('Clerk: OAuth flow was cancelled.')); + disposePendingOAuthFlow(new Error('Clerk: OAuth flow was canceled.')); app.removeListener('open-url', openUrlListener); app.removeListener('second-instance', secondInstanceListener); ipcMain.removeHandler(OAUTH_TRANSPORT_CHANNELS.getRedirectUrl); diff --git a/packages/electron/src/main/passkey-handlers.ts b/packages/electron/src/main/passkey-handlers.ts index 78b86d0a90e..7260007048a 100644 --- a/packages/electron/src/main/passkey-handlers.ts +++ b/packages/electron/src/main/passkey-handlers.ts @@ -42,6 +42,7 @@ function loadNativeModule(): Promise { return nativeModulePromise; } +// american-spelling-ignore-next-line: cancelled -- code the @clerk/electron-passkeys native addon emits const NATIVE_ERROR_CODES: PasskeyNativeErrorCode[] = ['cancelled', 'invalid_rp', 'not_supported', 'timeout', 'unknown']; function isPasskeyIpcResult(value: unknown): value is PasskeyIpcResult { diff --git a/packages/electron/src/passkeys/__tests__/errors.test.ts b/packages/electron/src/passkeys/__tests__/errors.test.ts index 86230f3024b..07f5ac5f567 100644 --- a/packages/electron/src/passkeys/__tests__/errors.test.ts +++ b/packages/electron/src/passkeys/__tests__/errors.test.ts @@ -5,7 +5,9 @@ import { mapPasskeyIpcError } from '../shared/errors'; describe('mapPasskeyIpcError', () => { it.each<[PasskeyNativeErrorCode, 'create' | 'get', string]>([ + // american-spelling-ignore-next-line: cancelled -- code the @clerk/electron-passkeys native addon emits ['cancelled', 'create', 'passkey_registration_cancelled'], + // american-spelling-ignore-next-line: cancelled -- code the @clerk/electron-passkeys native addon emits ['cancelled', 'get', 'passkey_retrieval_cancelled'], ['invalid_rp', 'create', 'passkey_invalid_rpID_or_domain'], ['invalid_rp', 'get', 'passkey_invalid_rpID_or_domain'], diff --git a/packages/electron/src/passkeys/__tests__/index.test.ts b/packages/electron/src/passkeys/__tests__/index.test.ts index f493bbac7ee..152d1a09550 100644 --- a/packages/electron/src/passkeys/__tests__/index.test.ts +++ b/packages/electron/src/passkeys/__tests__/index.test.ts @@ -175,15 +175,15 @@ describe('createPasskeys', () => { it('does not retry natively when the user cancels in the renderer', async () => { const bridge = makeBridge(); stubEnvironment({ bridge }); - const cancelled = { + const canceled = { publicKeyCredential: null, - error: Object.assign(new Error('cancelled'), { code: 'passkey_registration_cancelled' }), + error: Object.assign(new Error('canceled'), { code: 'passkey_registration_cancelled' }), }; - vi.mocked(webAuthnCreateCredential).mockResolvedValue(cancelled as never); + vi.mocked(webAuthnCreateCredential).mockResolvedValue(canceled as never); const result = await createPasskeys().create(creationOptions()); - expect(result).toBe(cancelled); + expect(result).toBe(canceled); expect(bridge.create).not.toHaveBeenCalled(); }); @@ -209,6 +209,7 @@ describe('createPasskeys', () => { create: vi.fn(() => Promise.resolve({ ok: false as const, + // american-spelling-ignore-next-line: cancelled -- code the @clerk/electron-passkeys native addon emits error: { code: 'cancelled' as const, message: 'user cancelled' }, }), ), diff --git a/packages/electron/src/passkeys/shared/errors.ts b/packages/electron/src/passkeys/shared/errors.ts index bfcaf5385fb..a9dbf44a997 100644 --- a/packages/electron/src/passkeys/shared/errors.ts +++ b/packages/electron/src/passkeys/shared/errors.ts @@ -12,6 +12,7 @@ export function mapPasskeyIpcError( const { code, message } = error; switch (code) { + // american-spelling-ignore-next-line: cancelled -- code the @clerk/electron-passkeys native addon emits case 'cancelled': return new ClerkWebAuthnError(message, { code: action === 'create' ? 'passkey_registration_cancelled' : 'passkey_retrieval_cancelled', diff --git a/packages/electron/src/shared/types.ts b/packages/electron/src/shared/types.ts index 4113cd6fb4c..dc52ddbdefd 100644 --- a/packages/electron/src/shared/types.ts +++ b/packages/electron/src/shared/types.ts @@ -147,6 +147,7 @@ export type AuthenticationResponseJSON = { }; // Native error codes that the renderer maps to ClerkWebAuthnError codes. +// american-spelling-ignore-next-line: cancelled -- code the @clerk/electron-passkeys native addon emits export type PasskeyNativeErrorCode = 'cancelled' | 'invalid_rp' | 'not_supported' | 'timeout' | 'unknown'; // Keep errors inside the response; Electron loses structure when invoke promises reject. diff --git a/packages/electron/src/storage/__tests__/index.test.ts b/packages/electron/src/storage/__tests__/index.test.ts index 133f5593c45..a8f4af0d824 100644 --- a/packages/electron/src/storage/__tests__/index.test.ts +++ b/packages/electron/src/storage/__tests__/index.test.ts @@ -373,7 +373,7 @@ describe('setItem', () => { }); it('resolves the cipher lazily and retries when it was initially unavailable (e.g. before app ready)', async () => { - // Unavailable on the first probe (pre-`ready`), available afterwards. + // Unavailable on the first probe (pre-`ready`), available afterward. ss.isEncryptionAvailable = vi.fn().mockReturnValueOnce(false).mockReturnValue(true); ss.encryptString = vi.fn((value: string) => Buffer.from(`enc(${value})`)); ss.decryptString = vi.fn(); diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index e9516aabfb7..aa9c1288a87 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -224,7 +224,7 @@ For each unprotected resource it flags, the rule offers an editor quick-fix sugg ## Bulk auto-fixing > [!WARNING] -> Applying these fixes changes the runtime behavior of your application — `await auth.protect()` enforces authentication where there potentially was none, or might override custom auth checks that were already in place. Always review the changes and test your application afterwards. +> Applying these fixes changes the runtime behavior of your application — `await auth.protect()` enforces authentication where there potentially was none, or might override custom auth checks that were already in place. Always review the changes and test your application afterward. Because the protection insertion is a suggestion rather than an autofix, `eslint --fix` deliberately won't apply it. To apply it across many files at once, use the bundled command, which lints with your existing ESLint config (so your protected/public globs are honored) and applies the suggestion to every resource it can safely fix: diff --git a/packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts b/packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts index b2ac7144685..8a3c616e7c2 100644 --- a/packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts +++ b/packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts @@ -1,6 +1,6 @@ import NativeClerkGoogleSignIn from '../specs/NativeClerkGoogleSignIn'; import type { - CancelledResponse, + CanceledResponse, ConfigureParams, CreateAccountParams, ExplicitSignInParams, @@ -24,8 +24,8 @@ function getNativeModule(): NonNullable { /** * Check if a response indicates the user canceled the sign-in flow. */ -export function isCancelledResponse(response: OneTapResponse): response is CancelledResponse { - return response.type === 'cancelled'; +export function isCanceledResponse(response: OneTapResponse): response is CanceledResponse { + return response.type === 'canceled'; } /** @@ -58,12 +58,13 @@ export function isErrorWithCode(error: unknown): error is { code: string; messag // Android's Credential Manager reports provider failures through the same cancellation exception it // uses for a dismissed chooser. Only Google Play services prefixes its messages with a status code, +// american-spelling-ignore-next-line: cancelled -- message the native Google sign-in modules emit // so a prefixed message that does not say "cancelled by user" is a failure rather than a dismissal. const PLAY_SERVICES_STATUS_PREFIX = /^\s*(?:\[\d+]|\d+:)/; -const CANCELLED_BY_USER = /cancell?ed by user/i; +const CANCELED_BY_USER = /cancell?ed by user/i; function rethrowIfProviderFailure(error: { code: string; message: string }): void { - if (!PLAY_SERVICES_STATUS_PREFIX.test(error.message) || CANCELLED_BY_USER.test(error.message)) { + if (!PLAY_SERVICES_STATUS_PREFIX.test(error.message) || CANCELED_BY_USER.test(error.message)) { return; } @@ -112,7 +113,7 @@ export const ClerkGoogleOneTapSignIn = { if (isErrorWithCode(error)) { if (error.code === 'SIGN_IN_CANCELLED') { rethrowIfProviderFailure(error); - return { type: 'cancelled', data: null }; + return { type: 'canceled', data: null }; } if (error.code === 'NO_SAVED_CREDENTIAL_FOUND') { return { type: 'noSavedCredentialFound', data: null }; @@ -140,7 +141,7 @@ export const ClerkGoogleOneTapSignIn = { if (isErrorWithCode(error)) { if (error.code === 'SIGN_IN_CANCELLED') { rethrowIfProviderFailure(error); - return { type: 'cancelled', data: null }; + return { type: 'canceled', data: null }; } if (error.code === 'NO_SAVED_CREDENTIAL_FOUND') { return { type: 'noSavedCredentialFound', data: null }; @@ -168,7 +169,7 @@ export const ClerkGoogleOneTapSignIn = { if (isErrorWithCode(error)) { if (error.code === 'SIGN_IN_CANCELLED') { rethrowIfProviderFailure(error); - return { type: 'cancelled', data: null }; + return { type: 'canceled', data: null }; } } throw error; @@ -192,7 +193,7 @@ export type { ExplicitSignInParams, OneTapResponse, OneTapSuccessResponse, - CancelledResponse, + CanceledResponse, NoSavedCredentialFound, GoogleUser, } from './types'; diff --git a/packages/expo/src/google-one-tap/__tests__/ClerkGoogleOneTapSignIn.test.ts b/packages/expo/src/google-one-tap/__tests__/ClerkGoogleOneTapSignIn.test.ts index 09b29e805a4..9f9d78827fa 100644 --- a/packages/expo/src/google-one-tap/__tests__/ClerkGoogleOneTapSignIn.test.ts +++ b/packages/expo/src/google-one-tap/__tests__/ClerkGoogleOneTapSignIn.test.ts @@ -34,15 +34,19 @@ describe('ClerkGoogleOneTapSignIn', () => { describe.each(methods)('%s', (_name, nativeMethod, call) => { // Messages androidx.credentials and Play services emit when the user dismisses the chooser. test.each([ + // american-spelling-ignore-next-line: cancelled -- message the native Google sign-in modules emit 'User cancelled the sign-in flow', + // american-spelling-ignore-next-line: cancelled -- message the native Google sign-in modules emit 'activity is cancelled by the user.', + // american-spelling-ignore-next-line: cancelled -- message the native Google sign-in modules emit 'User cancelled the selector', + // american-spelling-ignore-next-line: cancelled -- message the native Google sign-in modules emit '[16] Cancelled by user.', '[16] Canceled by user.', ])('treats %j as a cancellation', async message => { nativeMethod.mockRejectedValue(nativeError(message)); - await expect(call()).resolves.toEqual({ type: 'cancelled', data: null }); + await expect(call()).resolves.toEqual({ type: 'canceled', data: null }); }); // Play services reuses status 16 for failures the user did not trigger. diff --git a/packages/expo/src/google-one-tap/index.ts b/packages/expo/src/google-one-tap/index.ts index 1877117ef61..07d5fe580e2 100644 --- a/packages/expo/src/google-one-tap/index.ts +++ b/packages/expo/src/google-one-tap/index.ts @@ -1,6 +1,6 @@ export { ClerkGoogleOneTapSignIn, - isCancelledResponse, + isCanceledResponse, isNoSavedCredentialFoundResponse, isSuccessResponse, isErrorWithCode, @@ -13,7 +13,7 @@ export type { ExplicitSignInParams, OneTapResponse, OneTapSuccessResponse, - CancelledResponse, + CanceledResponse, NoSavedCredentialFound, GoogleUser, GoogleSignInError, diff --git a/packages/expo/src/google-one-tap/types.ts b/packages/expo/src/google-one-tap/types.ts index 23d5c78a78e..2d09fcefbd3 100644 --- a/packages/expo/src/google-one-tap/types.ts +++ b/packages/expo/src/google-one-tap/types.ts @@ -144,8 +144,8 @@ export type OneTapSuccessResponse = { /** * Response when the user cancels the sign-in flow. */ -export type CancelledResponse = { - type: 'cancelled'; +export type CanceledResponse = { + type: 'canceled'; data: null; }; @@ -160,7 +160,7 @@ export type NoSavedCredentialFound = { /** * Union type for all possible One Tap responses. */ -export type OneTapResponse = OneTapSuccessResponse | CancelledResponse | NoSavedCredentialFound; +export type OneTapResponse = OneTapSuccessResponse | CanceledResponse | NoSavedCredentialFound; /** * Error codes that can be thrown by the Google Sign-In module. diff --git a/packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts b/packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts index 36c38445505..7b533fa2b1a 100644 --- a/packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts +++ b/packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts @@ -204,7 +204,7 @@ describe('useSignInWithGoogle', () => { test('should handle user cancellation gracefully', async () => { mocks.ClerkGoogleOneTapSignIn.presentExplicitSignIn.mockResolvedValue({ - type: 'cancelled', + type: 'canceled', data: null, }); mocks.isSuccessResponse.mockReturnValue(false); diff --git a/packages/expo/src/hooks/useSignInWithGoogle.shared.ts b/packages/expo/src/hooks/useSignInWithGoogle.shared.ts index c2c7dabcb15..1a57415e137 100644 --- a/packages/expo/src/hooks/useSignInWithGoogle.shared.ts +++ b/packages/expo/src/hooks/useSignInWithGoogle.shared.ts @@ -130,7 +130,7 @@ export async function executeGoogleAuthenticationFlow( nonce, }); - // User cancelled + // User canceled if (!isSuccessResponse(response)) { return { createdSessionId: null, diff --git a/packages/expo/src/local-credentials/useLocalCredentials/__tests__/useLocalCredentials.test.ts b/packages/expo/src/local-credentials/useLocalCredentials/__tests__/useLocalCredentials.test.ts index 9cdcc567bf0..6318690ffcf 100644 --- a/packages/expo/src/local-credentials/useLocalCredentials/__tests__/useLocalCredentials.test.ts +++ b/packages/expo/src/local-credentials/useLocalCredentials/__tests__/useLocalCredentials.test.ts @@ -97,7 +97,7 @@ describe('useLocalCredentials', () => { expect(mocks.store.get(passwordKey)).toBe('hunter2'); }); - test('does not report credentials when the biometric prompt is cancelled', async () => { + test('does not report credentials when the biometric prompt is canceled', async () => { mocks.rejectProtectedWrites = true; const { result } = renderHook(() => useLocalCredentials()); @@ -115,7 +115,7 @@ describe('useLocalCredentials', () => { expect(remounted.result.current.hasCredentials).toBe(false); }); - test('keeps the existing credentials when a password update is cancelled', async () => { + test('keeps the existing credentials when a password update is canceled', async () => { mocks.store.set(identifierKey, 'user@example.com'); mocks.store.set(passwordKey, 'hunter2'); mocks.rejectProtectedWrites = true; diff --git a/packages/expo/src/provider/nativeClientSync.tsx b/packages/expo/src/provider/nativeClientSync.tsx index 5f40ed5287c..2ae49b342e9 100644 --- a/packages/expo/src/provider/nativeClientSync.tsx +++ b/packages/expo/src/provider/nativeClientSync.tsx @@ -878,7 +878,7 @@ export function NativeClientSync({ const lastRecovery = lastUnauthenticatedRecoveryRef.current; if (lastRecovery !== undefined) { const elapsed = Date.now() - lastRecovery; - // A backwards clock jump makes elapsed negative; treat it as expired instead of waiting out the gap. + // A backward clock jump makes elapsed negative; treat it as expired instead of waiting out the gap. if (elapsed >= 0 && elapsed < unauthenticatedRecoveryCooldownMs) { return await originalHandleUnauthenticated(options); } diff --git a/packages/expo/src/provider/singleton/createClerkInstance.ts b/packages/expo/src/provider/singleton/createClerkInstance.ts index 56230604852..9679d4ec955 100644 --- a/packages/expo/src/provider/singleton/createClerkInstance.ts +++ b/packages/expo/src/provider/singleton/createClerkInstance.ts @@ -173,13 +173,13 @@ export function createClerkInstance(ClerkClass: typeof Clerk) { if (createResourceCache) { let resourceRetryTimer: ReturnType | undefined; - let resourceRetryCancelled = false; + let resourceRetryCanceled = false; let resourceRetryInFlight = false; let resourceRetryFailureCount = 0; let initialResourcesLoaded = false; __internal_cancelResourceRetries = () => { - resourceRetryCancelled = true; + resourceRetryCanceled = true; if (resourceRetryTimer !== undefined) { clearTimeout(resourceRetryTimer); resourceRetryTimer = undefined; @@ -188,7 +188,7 @@ export function createClerkInstance(ClerkClass: typeof Clerk) { const scheduleResourceRetry = (timeout: number) => { if ( - resourceRetryCancelled || + resourceRetryCanceled || initialResourcesLoaded || resourceRetryTimer !== undefined || resourceRetryInFlight @@ -198,7 +198,7 @@ export function createClerkInstance(ClerkClass: typeof Clerk) { resourceRetryTimer = setTimeout(() => { resourceRetryTimer = undefined; - if (resourceRetryCancelled || initialResourcesLoaded) { + if (resourceRetryCanceled || initialResourcesLoaded) { return; } void retryInitializeResourcesFromFAPI(); @@ -206,7 +206,7 @@ export function createClerkInstance(ClerkClass: typeof Clerk) { }; const retryInitializeResourcesFromFAPI = async () => { - if (resourceRetryCancelled || initialResourcesLoaded || resourceRetryInFlight) { + if (resourceRetryCanceled || initialResourcesLoaded || resourceRetryInFlight) { return; } diff --git a/packages/expo/src/types/index.ts b/packages/expo/src/types/index.ts index 7c31837db0f..a76f1884189 100644 --- a/packages/expo/src/types/index.ts +++ b/packages/expo/src/types/index.ts @@ -24,7 +24,7 @@ export type { ExplicitSignInParams, GoogleUser, OneTapSuccessResponse, - CancelledResponse, + CanceledResponse, NoSavedCredentialFound, OneTapResponse, GoogleSignInErrorCode, diff --git a/packages/localizations/package.json b/packages/localizations/package.json index 1673b5edc94..81ada4f5c9f 100644 --- a/packages/localizations/package.json +++ b/packages/localizations/package.json @@ -10,7 +10,7 @@ "passwordless", "session", "jwt", - "customisation", + "customization", "localizations" ], "homepage": "https://clerk.com/", diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index 34ba72cb85c..32c16509b6b 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -330,7 +330,7 @@ export const enUS: LocalizationResource = { neverSynced: 'Not synced yet', title: 'Directory sync', }, - syncStatus__cancelled: 'Cancelled', + syncStatus__cancelled: 'Canceled', syncStatus__failed: 'Failed', syncStatus__running: 'Running', syncStatus__succeeded: 'Succeeded', @@ -2150,8 +2150,8 @@ export const enUS: LocalizationResource = { passkey_already_exists: 'A passkey is already registered with this device.', passkey_not_supported: 'Passkeys are not supported on this device.', passkey_pa_not_supported: 'Registration requires a platform authenticator but the device does not support it.', - passkey_registration_cancelled: 'Passkey registration was cancelled or timed out.', - passkey_retrieval_cancelled: 'Passkey verification was cancelled or timed out.', + passkey_registration_cancelled: 'Passkey registration was canceled or timed out.', + passkey_retrieval_cancelled: 'Passkey verification was canceled or timed out.', passwordComplexity: { maximumLength: 'less than {{length}} characters', minimumLength: '{{length}} or more characters', diff --git a/packages/mosaic/src/blocks/confirmation/confirmation.controller.test.ts b/packages/mosaic/src/blocks/confirmation/confirmation.controller.test.ts index 849034f9a0d..c6c34ae9c2f 100644 --- a/packages/mosaic/src/blocks/confirmation/confirmation.controller.test.ts +++ b/packages/mosaic/src/blocks/confirmation/confirmation.controller.test.ts @@ -66,7 +66,7 @@ describe('confirmationMachine', () => { expect(actor.getSnapshot().context.error).toEqual({ cause }); }); - it('drops the error when cancelled, so the next open starts clean', async () => { + it('drops the error when canceled, so the next open starts clean', async () => { const actor = start(); actor.send({ type: 'CONFIRM', run: () => Promise.reject(blocked) }); await vi.waitFor(() => expect(actor.getSnapshot().context.error).toBeDefined()); diff --git a/packages/mosaic/src/blocks/confirmation/confirmation.tsx b/packages/mosaic/src/blocks/confirmation/confirmation.tsx index 586fb6f9a0a..0f08f734738 100644 --- a/packages/mosaic/src/blocks/confirmation/confirmation.tsx +++ b/packages/mosaic/src/blocks/confirmation/confirmation.tsx @@ -94,7 +94,7 @@ export interface ConfirmationControlledProps { trigger?: DialogTriggerProps['render']; /** Dialog heading */ title: string; - /** What the action does and why it warrants a second look. Takes markup, for a name to emphasise */ + /** What the action does and why it warrants a second look. Takes markup, for a name to emphasize */ description: ReactNode; /** Text of the confirming button */ actionLabel: string; @@ -168,7 +168,7 @@ export interface ConfirmationHandleProps { finalFocus?: DialogFocusTarget; /** Dialog heading, or a function of the payload */ title: FromPayload; - /** What the action does and why it warrants a second look, or a function of the payload. Takes markup, for a name to emphasise */ + /** What the action does and why it warrants a second look, or a function of the payload. Takes markup, for a name to emphasize */ description: FromPayload; /** Text of the confirming button, or a function of the payload */ actionLabel: FromPayload; diff --git a/packages/mosaic/src/blocks/destructive/destructive.controller.test.ts b/packages/mosaic/src/blocks/destructive/destructive.controller.test.ts index 2360e9383cd..979cf3b2a5c 100644 --- a/packages/mosaic/src/blocks/destructive/destructive.controller.test.ts +++ b/packages/mosaic/src/blocks/destructive/destructive.controller.test.ts @@ -128,9 +128,9 @@ describe('useDestructiveController', () => { ); }); - it('closes without a message when reverification is cancelled', async () => { + it('closes without a message when reverification is canceled', async () => { const onDelete = vi.fn(() => - Promise.reject(new ClerkRuntimeError('cancelled', { code: 'reverification_cancelled' })), + Promise.reject(new ClerkRuntimeError('canceled', { code: 'reverification_cancelled' })), ); const { result } = renderHook(() => useDestructiveController({ onDelete, reverification: idleReverification })); act(() => result.current.onOpenChange(true)); diff --git a/packages/mosaic/src/components/action-bar/action-bar.test.tsx b/packages/mosaic/src/components/action-bar/action-bar.test.tsx index 4062f773f84..9c381a86e2a 100644 --- a/packages/mosaic/src/components/action-bar/action-bar.test.tsx +++ b/packages/mosaic/src/components/action-bar/action-bar.test.tsx @@ -69,7 +69,7 @@ afterEach(() => { }); describe('Mosaic ActionBar', () => { - it('renders a labelled toolbar tied to its table and described by its count', () => { + it('renders a labeled toolbar tied to its table and described by its count', () => { render(); const bar = screen.getByRole('toolbar', { name: 'Bulk actions' }); expect(bar).toHaveClass('cl-action-bar'); @@ -243,7 +243,7 @@ describe('Mosaic ActionBar', () => { await waitFor(() => expect(screen.getByRole('button', { name: 'Next row' })).toHaveFocus()); }); - it('dismisses with a labelled button', async () => { + it('dismisses with a labeled button', async () => { const onDismiss = vi.fn(); render(); await userEvent.click(screen.getByRole('button', { name: 'Clear selection' })); @@ -348,7 +348,7 @@ describe('Mosaic ActionBar', () => { ); } - it('returns focus to the bar when the dialog is cancelled', async () => { + it('returns focus to the bar when the dialog is canceled', async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole('checkbox', { name: 'Select Kyle' })); diff --git a/packages/mosaic/src/components/banner/banner.tsx b/packages/mosaic/src/components/banner/banner.tsx index 38b068afe4d..03c38f4967b 100644 --- a/packages/mosaic/src/components/banner/banner.tsx +++ b/packages/mosaic/src/components/banner/banner.tsx @@ -23,7 +23,7 @@ const BannerColorContext = React.createContext(DEFAULT_COLOR); /** Props for the banner surface, including native `div` props and the Mosaic `render` escape hatch. */ export interface BannerRootProps extends MosaicComponentProps<'div'> { - /** Semantic colour of the fill, border, icon, and copy. @default 'neutral' */ + /** Semantic color of the fill, border, icon, and copy. @default 'neutral' */ color?: BannerColor; } diff --git a/packages/mosaic/src/components/button/submit-button.tsx b/packages/mosaic/src/components/button/submit-button.tsx index 13c4452c36f..091d6b18c93 100644 --- a/packages/mosaic/src/components/button/submit-button.tsx +++ b/packages/mosaic/src/components/button/submit-button.tsx @@ -64,7 +64,7 @@ export const SubmitButton = React.forwardRef) => { if (isPending) { // `aria-disabled` is advisory — it doesn't stop the native submit — so the press is - // cancelled here instead. The `disabled` attribute would do both, but it drops the button + // canceled here instead. The `disabled` attribute would do both, but it drops the button // out of the tab order mid-action, taking focus with it just as the spinner is announced. // // TODO: fold this into the headless button's `focusableWhenDisabled` once it lands diff --git a/packages/mosaic/src/components/card/card.test.tsx b/packages/mosaic/src/components/card/card.test.tsx index 5a65e2953be..1ad8d61e965 100644 --- a/packages/mosaic/src/components/card/card.test.tsx +++ b/packages/mosaic/src/components/card/card.test.tsx @@ -364,7 +364,7 @@ describe('Mosaic Card', () => { }); // Nothing above named the card, so the parts carry no borrowed id. - it('leaves the title and description unidentified outside a labelled surface', () => { + it('leaves the title and description unidentified outside a labeled surface', () => { render( Review terms diff --git a/packages/mosaic/src/components/dialog/alert-dialog.test.tsx b/packages/mosaic/src/components/dialog/alert-dialog.test.tsx index 91bbe33279a..7f751daa9dc 100644 --- a/packages/mosaic/src/components/dialog/alert-dialog.test.tsx +++ b/packages/mosaic/src/components/dialog/alert-dialog.test.tsx @@ -196,7 +196,7 @@ describe('focus', () => { }); }); -// The dismissal policy is the behavioural half of what makes this an alert dialog: it cannot be +// The dismissal policy is the behavioral half of what makes this an alert dialog: it cannot be // answered by clicking next to it, but Escape — the keyboard's cancel — still works. describe('dismissal', () => { it('does not close on an outside press', async () => { diff --git a/packages/mosaic/src/components/dialog/dialog.styles.ts b/packages/mosaic/src/components/dialog/dialog.styles.ts index 0d5de836281..047ded0e8bb 100644 --- a/packages/mosaic/src/components/dialog/dialog.styles.ts +++ b/packages/mosaic/src/components/dialog/dialog.styles.ts @@ -38,7 +38,7 @@ const DESK = '@container cl-dialog (48rem <= width < 90rem)'; const WIDE = '@container cl-dialog (width >= 90rem)'; export const styles = stylex.create({ - // The scrim. Black in both schemes. A grey veil was tried for dark mode — lightening a dark page rather + // The scrim. Black in both schemes. A gray veil was tried for dark mode — lightening a dark page rather // than darkening it — and it read as haze over the page rather than as a surface lifting off it. // // A dialog opened over a `profile` or a `card` paints its OWN scrim, lighter than the base because @@ -161,7 +161,7 @@ export const styles = stylex.create({ * * A veil rather than `opacity` on the popup, because those are different effects. Fading the * popup fades the SURFACE — its background and its shadow — and the scrim shows through, which - * reads as the dialog dissolving. Painting the background colour back over the contents leaves + * reads as the dialog dissolving. Painting the background color back over the contents leaves * the surface at full strength and dims only what sits on it. * * Driven by a private custom property rather than by a state branch on the pseudo-element: @@ -530,7 +530,7 @@ const SHEET_EXIT_EASE = 'ease-out'; // The entering/exiting scale. `transform: scale()` scales the RENDERED border-radius along with // everything else, so a surface at 0.94 draws its corners at 94% of their value for the length of -// the transition — about 0.77px on a 12px radius. An earlier version cancelled that by dividing +// the transition — about 0.77px on a 12px radius. An earlier version canceled that by dividing // the popup's radius by the same factor, which only reaches corners the POPUP paints: since a // `card` and a `profile` are painted by the surface inside, the correction had stopped reaching the // corners that matter and was dropped rather than pushed into every surface's API. If it comes @@ -602,7 +602,7 @@ export const popupMotion = stylex.create({ // ANIMATION, not for no distinction: `transitionProperty` below narrows to `opacity` in // this mode, so the recede lands in one frame with nothing interpolating. Dropping it // outright leaves a stacked sheet sitting on an identical sheet with no scrim between - // them, which reads as a rendering fault rather than as a preference being honoured. + // them, which reads as a rendering fault rather than as a preference being honored. '@media (prefers-reduced-motion: reduce)': { default: 'scale(1)', ':where([data-stack-base])': `scale(${STACK_SCALE}) translateY(${STACK_LIFT})`, @@ -616,7 +616,7 @@ export const popupMotion = stylex.create({ // // The FIRST slot tracks the fourth on the phone branch rather than staying at `fast`: opacity // and the slide are one gesture there, and a fade that finishes while the surface is still - // travelling reads as a flash rather than as an arrival. Above the phone band the fade keeps + // traveling reads as a flash rather than as an arrival. Above the phone band the fade keeps // `fast` and lands with the scrim, since the scale it accompanies barely moves. The list is // positional against `transitionProperty` below. // @@ -696,7 +696,7 @@ export const popupMotion = stylex.create({ // ANIMATION, not for no distinction. `transitionProperty` below narrows to `opacity` in this // mode, so the recede lands in one frame with nothing interpolating. Dropping it outright // leaves a stacked dialog sitting on an identical one with no scrim between them, which - // reads as a rendering fault rather than as a preference being honoured. + // reads as a rendering fault rather than as a preference being honored. '@media (prefers-reduced-motion: reduce)': { default: 'scale(1)', ':where([data-stack-base])': `scale(${STACK_SCALE}) translateY(${STACK_LIFT})`, diff --git a/packages/mosaic/src/components/dialog/dialog.test.tsx b/packages/mosaic/src/components/dialog/dialog.test.tsx index 6c6b79b6ae7..1d5343f62b8 100644 --- a/packages/mosaic/src/components/dialog/dialog.test.tsx +++ b/packages/mosaic/src/components/dialog/dialog.test.tsx @@ -590,7 +590,7 @@ describe('compactPlacement', () => { }); }); -describe('viewport scroll behaviour', () => { +describe('viewport scroll behavior', () => { // The inside/outside scroll split. A pinned `height: 100%` cannot grow, so an over-tall popup // spills past the viewport's padding box and loses the bottom inset; `min-height: 100%` lets the // box grow with it. Which one applies follows from the variant, so what this pins is that the diff --git a/packages/mosaic/src/components/field/field.test.tsx b/packages/mosaic/src/components/field/field.test.tsx index 07724fd91f5..0bddcc7c7df 100644 --- a/packages/mosaic/src/components/field/field.test.tsx +++ b/packages/mosaic/src/components/field/field.test.tsx @@ -694,7 +694,7 @@ describe('Mosaic Field', () => { expect(screen.queryByText('Enter a valid email.')).toBeNull(); }); - it('renders a span label that focuses a control which cannot be natively labelled', async () => { + it('renders a span label that focuses a control which cannot be natively labeled', async () => { const user = userEvent.setup(); const consoleWarn = vi.spyOn(console, 'warn').mockImplementation(() => undefined); render( diff --git a/packages/mosaic/src/components/menu/menu.tsx b/packages/mosaic/src/components/menu/menu.tsx index 0035fe6640c..5d33beac351 100644 --- a/packages/mosaic/src/components/menu/menu.tsx +++ b/packages/mosaic/src/components/menu/menu.tsx @@ -34,7 +34,7 @@ export type MenuTriggerProps = MosaicComponentProps<'button'> & { /** * Opens the menu. Renders a ghost `Button` holding an ellipsis glyph by default; - * pass `children` for a labelled trigger, or `render` to supply your own element. + * pass `children` for a labeled trigger, or `render` to supply your own element. */ export const MenuTrigger = React.forwardRef(function MosaicMenuTrigger( { render, xstyle, children, focusableWhenDisabled, ...rest }, diff --git a/packages/mosaic/src/components/otp/otp.styles.ts b/packages/mosaic/src/components/otp/otp.styles.ts index 42887c5a8e3..c62e8bb00dd 100644 --- a/packages/mosaic/src/components/otp/otp.styles.ts +++ b/packages/mosaic/src/components/otp/otp.styles.ts @@ -32,7 +32,7 @@ export const styles = stylex.create({ height: space['14'], width: '100%', }, - // The success cell restates the focus shadow so a verified code keeps its colour + // The success cell restates the focus shadow so a verified code keeps its color // while focused, mirroring how the shared surface handles `aria-invalid`. success: { borderColor: { diff --git a/packages/mosaic/src/components/otp/otp.tsx b/packages/mosaic/src/components/otp/otp.tsx index 23fb1721315..d5538ca595f 100644 --- a/packages/mosaic/src/components/otp/otp.tsx +++ b/packages/mosaic/src/components/otp/otp.tsx @@ -17,7 +17,7 @@ export interface OtpProps extends Omit, MosaicStyleProps { /** The number of boxes in the code. @default 6 */ length?: number; - /** Colours every slot for the verification outcome. Defaults to the enclosing `Field`'s validity. */ + /** Colors every slot for the verification outcome. Defaults to the enclosing `Field`'s validity. */ status?: OtpStatus; } diff --git a/packages/mosaic/src/components/popover/popover.styles.ts b/packages/mosaic/src/components/popover/popover.styles.ts index d0388b59899..241fed1a15c 100644 --- a/packages/mosaic/src/components/popover/popover.styles.ts +++ b/packages/mosaic/src/components/popover/popover.styles.ts @@ -48,7 +48,7 @@ export const styles = stylex.create({ // the popup. Falls back to `center` for the first frame, which is still `opacity: 0`. transformOrigin: 'var(--cl-anchor-origin, center)', // The exit is shorter than the entrance: an arrival earns a moment to settle, a - // dismissal is an acknowledgement and wants to be out of the way. + // dismissal is an acknowledgment and wants to be out of the way. // // On the way in the fade finishes first (positional, so `fast` is opacity and `base` // is transform). It lands opaque just as the scale reaches full size, leaving the @@ -73,7 +73,7 @@ export const styles = stylex.create({ default: 'opacity, transform', '@media (prefers-reduced-motion: reduce)': 'opacity', }, - // Positional against `transitionProperty`; the entrance curve run backwards would + // Positional against `transitionProperty`; the entrance curve run backward would // stall the exit and turn the overshoot into a wobble. transitionTimingFunction: { default: `${easingVars['--cl-ease-enter']}, ${easingVars['--cl-ease-default']}`, diff --git a/packages/mosaic/src/components/profile/profile.test.tsx b/packages/mosaic/src/components/profile/profile.test.tsx index 6c1bfb4c6b0..abd2c2e63f5 100644 --- a/packages/mosaic/src/components/profile/profile.test.tsx +++ b/packages/mosaic/src/components/profile/profile.test.tsx @@ -127,7 +127,7 @@ describe('Profile', () => { expect(screen.getByTestId('security-badge')).toHaveAttribute('data-color', 'warning'); }); - it('is a labelled navigation of tabs beside the selected page', () => { + it('is a labeled navigation of tabs beside the selected page', () => { renderSurface(); const title = screen.getByRole('heading', { level: 2, name: 'User profile' }); diff --git a/packages/mosaic/src/components/scroll-area/scroll-area.styles.ts b/packages/mosaic/src/components/scroll-area/scroll-area.styles.ts index cd0937315b3..fe2524a2d8e 100644 --- a/packages/mosaic/src/components/scroll-area/scroll-area.styles.ts +++ b/packages/mosaic/src/components/scroll-area/scroll-area.styles.ts @@ -16,7 +16,7 @@ const thumbColor = scrollbarThumbVars['--_cl-scrollbar-thumb-color']; // One animation per edge, each writing its own progress var. The end fade counts DOWN // rather than running `animation-direction: reverse`: with `fill-mode: both` the two are -// equivalent (the backwards fill holds the `from` frame, so the fade reads 1 for the whole +// equivalent (the backward fill holds the `from` frame, so the fade reads 1 for the whole // scroll and only drops across the final `fade-range`), and writing it into the keyframes // keeps `animation-direction` off the element entirely. // The suppressions work around a gap in StyleX's own types, not a problem with the CSS: @@ -79,7 +79,7 @@ const styles = stylex.create({ }, /** - * The thumb's colour, produced HERE on the scroller rather than on the pseudo-element that + * The thumb's color, produced HERE on the scroller rather than on the pseudo-element that * paints it, because Blink does not run transitions declared on `::-webkit-scrollbar-thumb` — * verified by hand, and the reason Polaris declares its own on the scroller too. A registered * custom property set here animates and inherits into the pseudo-element, which only reads it. @@ -90,7 +90,7 @@ const styles = stylex.create({ * the scroller — including a consumer retargeting `--cl-scrollbar-thumb` on the region's * `:hover` to fade the bar in — transitions through this declaration. * - * `linear` because this is a colour: an ease on top of an already perceptually non-uniform + * `linear` because this is a color: an ease on top of an already perceptually non-uniform * interpolation only makes the midpoint drag. */ thumbColor: { @@ -122,7 +122,7 @@ const styles = stylex.create({ * alone, so the thumb reads as floating over the content rather than riding in a rail. * * Every declaration here repeats `{ default: null, '@media (pointer: fine)': … }`. A touch - * platform draws an overlay bar there is no width or colour to apply to, and — the reason the + * platform draws an overlay bar there is no width or color to apply to, and — the reason the * gate has to reach the SHAPE properties too, not just the visible ones — Blink switches an * element to a custom scrollbar the moment ANY `::-webkit-scrollbar*` rule matches it, which * would trade that overlay bar for a permanent one. `null` emits no declaration at all, so @@ -135,7 +135,7 @@ const styles = stylex.create({ * either makes a UA ignore the `::-webkit-scrollbar*` family entirely, so keeping them would * leave every rule here as dead code in exactly the engines that implement it. Firefox * implements the pseudo-elements not at all and keeps its platform scrollbar. That is the whole - * cost of the trade, and it buys per-state thumb colours and a real pixel width, neither of + * cost of the trade, and it buys per-state thumb colors and a real pixel width, neither of * which the standard properties can express. * * The thumb's states are COMBINED keys rather than a `:hover` nested inside the @@ -170,10 +170,10 @@ const styles = stylex.create({ backgroundColor: { default: null, '@media (pointer: fine)': { - // eslint-disable-next-line @stylexjs/valid-styles -- valid-styles doesn't resolve a `stylex.types.color()` var to a colour; the compiler does. + // eslint-disable-next-line @stylexjs/valid-styles -- valid-styles doesn't resolve a `stylex.types.color()` var to a color; the compiler does. default: thumbColor, // No `scrollbar-color: auto` lever survives on this path, so forced colors need their - // own answer: pin the thumb to a system colour rather than let a themed one lose its + // own answer: pin the thumb to a system color rather than let a themed one lose its // contrast guarantee against a palette we no longer control. Declared on // `background-color` rather than on the var so it holds across all four states at once. '@media (forced-colors: active)': 'ButtonBorder', diff --git a/packages/mosaic/src/components/scroll-area/scroll-area.test.ts b/packages/mosaic/src/components/scroll-area/scroll-area.test.ts index 16f98b4f6a1..9403df5a754 100644 --- a/packages/mosaic/src/components/scroll-area/scroll-area.test.ts +++ b/packages/mosaic/src/components/scroll-area/scroll-area.test.ts @@ -11,7 +11,7 @@ describe('Mosaic scroll area styles', () => { }); // The atoms carry the gutter, so the two have to be distinguishable — an accidental - // collapse would silently give every scroll surface the same overflow behaviour. + // collapse would silently give every scroll surface the same overflow behavior. it('varies the gutter atom by argument', () => { expect(scrollAreaViewport('stable')).not.toEqual(scrollAreaViewport('auto')); }); @@ -60,7 +60,7 @@ describe('Mosaic scroll area styles', () => { // The counterpart to the assertion above: `--_cl-` is the marker for plumbing, so it must not // drift into the themable `--cl-` namespace the way a rename easily could. - it('keeps the thumb colour carrier out of the public token namespace', () => { + it('keeps the thumb color carrier out of the public token namespace', () => { expect(scrollbarThumbVars).toMatchObject({ '--_cl-scrollbar-thumb-color': 'var(--_cl-scrollbar-thumb-color)', }); diff --git a/packages/mosaic/src/components/scroll-area/scroll-area.vars.stylex.ts b/packages/mosaic/src/components/scroll-area/scroll-area.vars.stylex.ts index f79165c9fa9..3c93064e53b 100644 --- a/packages/mosaic/src/components/scroll-area/scroll-area.vars.stylex.ts +++ b/packages/mosaic/src/components/scroll-area/scroll-area.vars.stylex.ts @@ -9,7 +9,7 @@ export const scrollAreaVars = stylex.defineVars({ '--cl-scroll-area-progress-end': stylex.types.number(0), }); -// The animated carrier for the scrollbar thumb's colour. `::-webkit-scrollbar-thumb` cannot +// The animated carrier for the scrollbar thumb's color. `::-webkit-scrollbar-thumb` cannot // transition properties of its own, so the transition is declared on the SCROLLER against this // property and the pseudo-element only ever reads it — `inherits: true`, which StyleX hardcodes // for typed vars, is what carries the animating value down into it. Same primitive as the diff --git a/packages/mosaic/src/components/select/select.tsx b/packages/mosaic/src/components/select/select.tsx index 593cb9a319c..fd04cccd87f 100644 --- a/packages/mosaic/src/components/select/select.tsx +++ b/packages/mosaic/src/components/select/select.tsx @@ -110,7 +110,7 @@ export const SelectTrigger = React.forwardRef ( @@ -129,7 +129,7 @@ export const SelectTrigger = React.forwardRef { }); describe('inside a Field', () => { - it('is labelled and described by the field', () => { + it('is labeled and described by the field', () => { render( Email diff --git a/packages/mosaic/src/features/reverification/__tests__/reverification.feature.test.tsx b/packages/mosaic/src/features/reverification/__tests__/reverification.feature.test.tsx index 71ce93a5b3d..1154b62ec9e 100644 --- a/packages/mosaic/src/features/reverification/__tests__/reverification.feature.test.tsx +++ b/packages/mosaic/src/features/reverification/__tests__/reverification.feature.test.tsx @@ -378,7 +378,7 @@ describe('Reverification', () => { expect(screen.getByText(GENERIC_ERROR_TITLE)).toBeVisible(); expect(outcome()).toBe(''); - // Closing it rejects the action as cancelled + // Closing it rejects the action as canceled await user.click(screen.getByRole('button', { name: 'Close' })); await waitFor(() => expect(outcome()).toBe('rejected: reverification_cancelled')); await untilClosed(); @@ -1034,7 +1034,7 @@ describe('Reverification', () => { expect(action).toHaveBeenCalledTimes(1); expect(outcome()).toBe(''); - // Dismissing it rejects the action as cancelled + // Dismissing it rejects the action as canceled await dismiss(user); await waitFor(() => expect(outcome()).toBe('rejected: reverification_cancelled')); expect(screen.queryByText(GENERIC_ERROR_TITLE)).toBeNull(); @@ -1220,7 +1220,7 @@ describe('Reverification', () => { await user.type(await screen.findByLabelText('Password'), `${PASSWORD}{Enter}`); await waitFor(() => expect(attempt.requests).toHaveLength(1)); - // The card closes and the action is rejected as cancelled + // The card closes and the action is rejected as canceled await dismiss(user); await waitFor(() => expect(outcome()).toBe('rejected: reverification_cancelled')); await untilClosed(); @@ -1244,7 +1244,7 @@ describe('Reverification', () => { await user.type(await screen.findByLabelText('Password'), `${PASSWORD}{Enter}`); await waitFor(() => expect(attempt.requests).toHaveLength(1)); - // The card closes and the action is rejected as cancelled + // The card closes and the action is rejected as canceled await dismiss(user); await waitFor(() => expect(outcome()).toBe('rejected: reverification_cancelled')); await untilClosed(); @@ -1324,7 +1324,7 @@ describe('Reverification', () => { // The card closes await untilClosed(); - // The action is rejected as cancelled + // The action is rejected as canceled await waitFor(() => expect(outcome()).toBe('rejected: reverification_cancelled')); }); diff --git a/packages/mosaic/src/features/reverification/__tests__/use-reverification-with-state.test.tsx b/packages/mosaic/src/features/reverification/__tests__/use-reverification-with-state.test.tsx index bf8ff60f4dc..fdccda08757 100644 --- a/packages/mosaic/src/features/reverification/__tests__/use-reverification-with-state.test.tsx +++ b/packages/mosaic/src/features/reverification/__tests__/use-reverification-with-state.test.tsx @@ -46,7 +46,7 @@ vi.mock('@clerk/shared/react', async importOriginal => { cancel: () => { challengeCancel(); reject( - new ClerkRuntimeError('User cancelled attempted verification', { + new ClerkRuntimeError('User canceled attempted verification', { code: 'reverification_cancelled', }), ); @@ -150,7 +150,7 @@ describe('useReverificationWithState', () => { expect(challengeCancel).not.toHaveBeenCalled(); }); - it('settles immediately when the active challenge is cancelled', async () => { + it('settles immediately when the active challenge is canceled', async () => { const fetcher = vi.fn().mockResolvedValue({ reverificationLevel: undefined } satisfies Hint); const { result } = renderHook(() => useReverificationWithState(fetcher)); diff --git a/packages/mosaic/src/features/reverification/reverification.controller.ts b/packages/mosaic/src/features/reverification/reverification.controller.ts index 43ea26a788a..afca6aaf236 100644 --- a/packages/mosaic/src/features/reverification/reverification.controller.ts +++ b/packages/mosaic/src/features/reverification/reverification.controller.ts @@ -424,7 +424,7 @@ function viewStep(value: string, method: ReverificationMethod | null): Reverific * retried, so it keeps the last factor pending and has no `onCancel`. The factor machine runs * while the model is active and resets when the model returns to inactive. * - * When the run ends after reverification was shown (completed, failed or cancelled), the model is + * When the run ends after reverification was shown (completed, failed or canceled), the model is * 'settled' and the controller keeps returning the last frame, pending and without `onCancel`, * until the next run starts or `reset` is called. Hosts decide when to stop rendering it. */ diff --git a/packages/mosaic/src/features/reverification/use-reverification-with-state.ts b/packages/mosaic/src/features/reverification/use-reverification-with-state.ts index ced1f18e220..88fe2d3678f 100644 --- a/packages/mosaic/src/features/reverification/use-reverification-with-state.ts +++ b/packages/mosaic/src/features/reverification/use-reverification-with-state.ts @@ -23,7 +23,7 @@ type RuntimeOperation = cancel: () => void; } | { status: 'retrying'; promise: Promise } - | { status: 'cancelling'; promise: Promise }; + | { status: 'canceling'; promise: Promise }; type Runtime = { operation: RuntimeOperation; @@ -92,7 +92,7 @@ export function useReverificationWithState( if (operation.status !== 'active') { return; } - runtimeRef.current.operation = { status: 'cancelling', promise: operation.promise }; + runtimeRef.current.operation = { status: 'canceling', promise: operation.promise }; setReverificationState({ phase: 'settled' }); operation.cancel(); }, []); diff --git a/packages/mosaic/src/features/user-button/__tests__/user-button.layout.test.ts b/packages/mosaic/src/features/user-button/__tests__/user-button.layout.test.ts index 3742988c16c..67eb215e8bf 100644 --- a/packages/mosaic/src/features/user-button/__tests__/user-button.layout.test.ts +++ b/packages/mosaic/src/features/user-button/__tests__/user-button.layout.test.ts @@ -87,7 +87,7 @@ describe('resolveUserButtonLayout, a combined surface', () => { }); describe('resolveUserButtonLayout, how the header carries its actions', () => { - it('stacks them wherever a labelled action joins the gear', () => { + it('stacks them wherever a labeled action joins the gear', () => { expect(resolve('combined').headerLayout).toBe('stacked'); expect(resolve('organization').headerLayout).toBe('stacked'); }); diff --git a/packages/mosaic/src/features/user-button/user-button-item.styles.ts b/packages/mosaic/src/features/user-button/user-button-item.styles.ts index ee8a7a9735a..54d230e64c6 100644 --- a/packages/mosaic/src/features/user-button/user-button-item.styles.ts +++ b/packages/mosaic/src/features/user-button/user-button-item.styles.ts @@ -92,7 +92,7 @@ export const styles = stylex.create({ // At least as wide as the `⋯` menu button that owns it, so whatever stands in that button's // place — the active check, a spinner — lands on the same center line and the right edge of every - // row holds still as rows change state. A labelled button or a note grows it past that, up to half + // row holds still as rows change state. A labeled button or a note grows it past that, up to half // the row, and truncates beyond it: the `minmax(0, auto)` columns let a button shrink below its // label, which a flex item holding `flexShrink: 0` never does. trailing: { diff --git a/packages/mosaic/src/features/user-button/user-button.styles.ts b/packages/mosaic/src/features/user-button/user-button.styles.ts index 29dce5c1688..19441821d89 100644 --- a/packages/mosaic/src/features/user-button/user-button.styles.ts +++ b/packages/mosaic/src/features/user-button/user-button.styles.ts @@ -22,7 +22,7 @@ export const styles = stylex.create({ minWidth: 0, }, - triggerLabelled: { + triggerLabeled: { padding: space['1'], gap: space['1.5'], backgroundColor: { diff --git a/packages/mosaic/src/features/user-button/user-button.types.ts b/packages/mosaic/src/features/user-button/user-button.types.ts index a715f6bffdb..79b6dab0b01 100644 --- a/packages/mosaic/src/features/user-button/user-button.types.ts +++ b/packages/mosaic/src/features/user-button/user-button.types.ts @@ -112,7 +112,7 @@ export type UserButtonMode = 'combined' | 'organization' | 'user'; /** * How the header carries its actions: `inline` trails the lead with them, the gear as an icon; - * `stacked` runs them under it as full-width labelled buttons. + * `stacked` runs them under it as full-width labeled buttons. */ export type UserButtonHeaderLayout = 'inline' | 'stacked'; diff --git a/packages/mosaic/src/features/user-button/user-button.view.tsx b/packages/mosaic/src/features/user-button/user-button.view.tsx index f69b7e83848..4e72f98ff1e 100644 --- a/packages/mosaic/src/features/user-button/user-button.view.tsx +++ b/packages/mosaic/src/features/user-button/user-button.view.tsx @@ -342,9 +342,9 @@ interface HeaderAction { id: UserButtonAction; label: string; icon: IconName; - /** Inline, the button is square and shows the icon alone, labelling itself through `aria-label`. */ + /** Inline, the button is square and shows the icon alone, labeling itself through `aria-label`. */ iconOnly?: boolean; - /** Shows the icon ahead of the label even inline, where a labelled button otherwise has none. */ + /** Shows the icon ahead of the label even inline, where a labeled button otherwise has none. */ leadingIcon?: boolean; onClick?: () => void; /** Opens a menu of these rather than acting itself. */ @@ -496,7 +496,7 @@ function Header() { } } } - // Inline, the gear trails the labelled actions; stacked, it leads them. + // Inline, the gear trails the labeled actions; stacked, it leads them. const ordered = layout === 'stacked' ? [...actions.filter(a => a.id === 'manageLead'), ...actions.filter(a => a.id !== 'manageLead')] @@ -654,7 +654,7 @@ function PendingRow({ busyKey, name, imageUrl, actionLabel, onAccept, note }: Pe note ? ( {note} ) : onAccept ? ( - // Every other affordance here swaps its icon for a spinner, but this one is a labelled + // Every other affordance here swaps its icon for a spinner, but this one is a labeled // button, so the spinner goes inside it rather than taking the row's trailing edge — the // press and the thing that reports it stay the same element. `pendingKey` is already // spin-delayed by the container, so this asks for no second delay of its own. @@ -1121,7 +1121,7 @@ export function UserButtonTrigger({ xstyle={[ ringsAvatar ? styles.triggerRinglessAvatar : focusOutline.visible, styles.trigger, - renderTriggerLabel ? styles.triggerLabelled : styles.triggerAvatarOnly, + renderTriggerLabel ? styles.triggerLabeled : styles.triggerAvatarOnly, !renderTriggerLabel && shape === 'circle' ? styles.triggerRound : null, ]} > diff --git a/packages/mosaic/src/features/user-profile/__tests__/user-profile-danger-section.integration.test.tsx b/packages/mosaic/src/features/user-profile/__tests__/user-profile-danger-section.integration.test.tsx index fe64d963b70..832c19e331e 100644 --- a/packages/mosaic/src/features/user-profile/__tests__/user-profile-danger-section.integration.test.tsx +++ b/packages/mosaic/src/features/user-profile/__tests__/user-profile-danger-section.integration.test.tsx @@ -153,7 +153,7 @@ describe('UserProfileDangerSection', () => { await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument()); }); - it('clears the phrase when the dialog is cancelled', async () => { + it('clears the phrase when the dialog is canceled', async () => { const user = userEvent.setup(); renderSection(); const dialog = await openDialog(user); diff --git a/packages/mosaic/src/features/user-profile/__tests__/user-profile-device-actions.test.tsx b/packages/mosaic/src/features/user-profile/__tests__/user-profile-device-actions.test.tsx index 5f5ea410364..2ee2d34786b 100644 --- a/packages/mosaic/src/features/user-profile/__tests__/user-profile-device-actions.test.tsx +++ b/packages/mosaic/src/features/user-profile/__tests__/user-profile-device-actions.test.tsx @@ -216,7 +216,7 @@ describe('signing out of all other devices', () => { await waitFor(() => expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()); }); - it('leaves the devices alone when the confirmation is cancelled', async () => { + it('leaves the devices alone when the confirmation is canceled', async () => { const user = userEvent.setup(); const onSignOutAllOtherDevices = vi.fn(); renderAll(onSignOutAllOtherDevices); @@ -377,7 +377,7 @@ describe('focus after signing a device out', () => { expect(screen.getByRole('button', { name: 'Manage Clerk App on macOS' })).toHaveFocus(); }); - it('still returns focus to the row itself when the sign out is cancelled', async () => { + it('still returns focus to the row itself when the sign out is canceled', async () => { const user = userEvent.setup(); render(); await openMenu(user, mobile); diff --git a/packages/mosaic/src/features/user-profile/__tests__/user-profile-mfa-section.view.test.tsx b/packages/mosaic/src/features/user-profile/__tests__/user-profile-mfa-section.view.test.tsx index 2036628b25b..5745c5d45f8 100644 --- a/packages/mosaic/src/features/user-profile/__tests__/user-profile-mfa-section.view.test.tsx +++ b/packages/mosaic/src/features/user-profile/__tests__/user-profile-mfa-section.view.test.tsx @@ -109,7 +109,7 @@ describe('MFA section', () => { expect(add).toHaveFocus(); }); - it('confirms the selected SMS method and restores focus when removal is cancelled', async () => { + it('confirms the selected SMS method and restores focus when removal is canceled', async () => { const user = userEvent.setup(); const { props } = renderView({ methods: [ diff --git a/packages/mosaic/src/features/user-profile/user-profile-active-devices-section.view.tsx b/packages/mosaic/src/features/user-profile/user-profile-active-devices-section.view.tsx index ec4e42d377b..5d889c28033 100644 --- a/packages/mosaic/src/features/user-profile/user-profile-active-devices-section.view.tsx +++ b/packages/mosaic/src/features/user-profile/user-profile-active-devices-section.view.tsx @@ -53,7 +53,7 @@ export function UserProfileActiveDevicesSectionView({ const signOutAllTrigger = useRef(null); // Confirming removes the other devices and the trigger with them — hence the dialog mounted - // outside the card, and the current device as the place focus lands. Cancelling keeps the + // outside the card, and the current device as the place focus lands. Canceling keeps the // trigger, so focus goes back to it. const focusAfterSignOutAll = () => { const signedOut = signedOutAll.current; diff --git a/packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-edit-password.controller.test.ts b/packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-edit-password.controller.test.ts index 59dd318febd..321d47f0a1b 100644 --- a/packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-edit-password.controller.test.ts +++ b/packages/mosaic/src/features/user-profile/user-profile-password-section/user-profile-edit-password.controller.test.ts @@ -68,7 +68,7 @@ describe('useUserProfileEditPasswordController timing', () => { expect(validatePassword).not.toHaveBeenCalled(); await act(() => vi.advanceTimersByTimeAsync(1)); expect(validatePassword).toHaveBeenCalledExactlyOnceWith('latest password'); - act(() => result.current.form.setValue('newPassword', 'cancelled password')); + act(() => result.current.form.setValue('newPassword', 'canceled password')); act(() => result.current.onOpenChange(false)); await act(() => vi.advanceTimersByTimeAsync(350)); expect(validatePassword).toHaveBeenCalledTimes(1); diff --git a/packages/mosaic/src/hooks/use-accessible-name-warning.ts b/packages/mosaic/src/hooks/use-accessible-name-warning.ts index a33914cafd2..4e60abe037e 100644 --- a/packages/mosaic/src/hooks/use-accessible-name-warning.ts +++ b/packages/mosaic/src/hooks/use-accessible-name-warning.ts @@ -33,8 +33,8 @@ export function useAccessibleNameWarning(node: HTMLElement | null, component: st // title part the attribute points at an id that is not in the document — which names the // dialog exactly as poorly as having no attribute at all, and is what a presence check would // wave through. `Popover` omits the attribute instead, so resolving covers both shapes. - const labelledBy = node.getAttribute('aria-labelledby'); - const named = labelledBy + const labeledBy = node.getAttribute('aria-labelledby'); + const named = labeledBy ?.split(/\s+/) .filter(Boolean) .some(id => node.ownerDocument.getElementById(id)?.textContent?.trim()); diff --git a/packages/mosaic/src/hooks/use-debounced-async.ts b/packages/mosaic/src/hooks/use-debounced-async.ts index 5a9f2b38f7e..07083a66d91 100644 --- a/packages/mosaic/src/hooks/use-debounced-async.ts +++ b/packages/mosaic/src/hooks/use-debounced-async.ts @@ -24,7 +24,7 @@ interface Settled { /** * Runs `run(value)` once `value` has stopped changing for `delayMs`, and returns the latest result. * The previous result stays visible while the next one is pending. A new value, disabling or - * unmounting aborts the `signal` and drops any result that arrives afterwards. + * unmounting aborts the `signal` and drops any result that arrives afterward. * * `value` is compared with `Object.is`, so it must be referentially stable across renders. An * object or array created during render (such as an un-memoized `[]` or `{}`) counts as a new diff --git a/packages/mosaic/src/hooks/use-spin-delay.ts b/packages/mosaic/src/hooks/use-spin-delay.ts index 985edc160f9..b045d9a3a56 100644 --- a/packages/mosaic/src/hooks/use-spin-delay.ts +++ b/packages/mosaic/src/hooks/use-spin-delay.ts @@ -14,7 +14,7 @@ const DEFAULT_MIN_DURATION = 400; * Spin-delays a nullable value: returns `null` until `value` has stayed non-null longer than `delay` * (so quick actions never flash a spinner), then holds the last non-null value for at least * `minDuration` after it clears (so the spinner never flickers off). A value-carrying rework of - * https://github.com/smeijer/spin-delay: each timer lives in a `const` and is cancelled by effect + * https://github.com/smeijer/spin-delay: each timer lives in a `const` and is canceled by effect * cleanup when `value` flips before it fires. */ export function useSpinDelay(value: T | null, options: SpinDelayOptions = {}): T | null { diff --git a/packages/mosaic/src/localization/errors.messages.ts b/packages/mosaic/src/localization/errors.messages.ts index 9efc9041a00..cd299abcde3 100644 --- a/packages/mosaic/src/localization/errors.messages.ts +++ b/packages/mosaic/src/localization/errors.messages.ts @@ -41,8 +41,8 @@ export const errorMessages: { readonly generic: string } & Readonly { expect(actor.getSnapshot().value).toBe('first'); vi.advanceTimersByTime(500); - expect(actor.getSnapshot().value).toBe('first'); // the 1000ms timer was cancelled + expect(actor.getSnapshot().value).toBe('first'); // the 1000ms timer was canceled }); it('passes AfterEvent with the correct delay to transition actions', () => { diff --git a/packages/mosaic/src/machine/__tests__/patterns.test.ts b/packages/mosaic/src/machine/__tests__/patterns.test.ts index 60688e9636f..39ce36ba001 100644 --- a/packages/mosaic/src/machine/__tests__/patterns.test.ts +++ b/packages/mosaic/src/machine/__tests__/patterns.test.ts @@ -618,7 +618,7 @@ describe('pattern: on-mount async — initial state invokes immediately', () => }, onError: { target: 'failed' }, }), - on: { CANCEL: 'cancelled' }, + on: { CANCEL: 'canceled' }, }, routing: { always: [ @@ -631,7 +631,7 @@ describe('pattern: on-mount async — initial state invokes immediately', () => secondFactor: {}, complete: { type: 'final' }, failed: {}, - cancelled: {}, + canceled: {}, }, }); } @@ -685,7 +685,7 @@ describe('pattern: on-mount async — initial state invokes immediately', () => expect(actor.getSnapshot().value).toBe('failed'); }); - it('can be cancelled while the invoke is in-flight — late resolve is a no-op', async () => { + it('can be canceled while the invoke is in-flight — late resolve is a no-op', async () => { const gate = deferred<{ status: string }>(); const actor = createActor( makeMachine(() => gate.promise), @@ -693,11 +693,11 @@ describe('pattern: on-mount async — initial state invokes immediately', () => ); actor.start(); actor.send({ type: 'CANCEL' }); - expect(actor.getSnapshot().value).toBe('cancelled'); + expect(actor.getSnapshot().value).toBe('canceled'); gate.resolve({ status: 'complete' }); await tick(); - // Machine stays in cancelled — the in-flight invoke was abandoned - expect(actor.getSnapshot().value).toBe('cancelled'); + // Machine stays in canceled — the in-flight invoke was abandoned + expect(actor.getSnapshot().value).toBe('canceled'); }); }); diff --git a/packages/mosaic/src/machine/assign.ts b/packages/mosaic/src/machine/assign.ts index a59754212ba..aebab106c0e 100644 --- a/packages/mosaic/src/machine/assign.ts +++ b/packages/mosaic/src/machine/assign.ts @@ -2,7 +2,7 @@ import type { AssignAction, EventObject } from './types'; import { ASSIGN } from './types'; /** - * Context-update action creator. The returned object is recognised by the + * Context-update action creator. The returned object is recognized by the * runtime, which shallow-merges the returned partial into context. * * ```ts diff --git a/packages/mosaic/src/machine/create-actor.ts b/packages/mosaic/src/machine/create-actor.ts index 1b577839796..8eeba50a40f 100644 --- a/packages/mosaic/src/machine/create-actor.ts +++ b/packages/mosaic/src/machine/create-actor.ts @@ -73,7 +73,7 @@ export function createActor const listeners: SnapshotListener[] = []; /** - * Normalise a raw `Transition` value into a `TransitionConfig[]` the runtime + * Normalize a raw `Transition` value into a `TransitionConfig[]` the runtime * can process uniformly. Handles all four arms of the `Transition` union: * - string → `{ target: string }` * - `TransitionConfig` → as-is diff --git a/packages/mosaic/src/machine/types.ts b/packages/mosaic/src/machine/types.ts index 82f00c0e076..9d152899120 100644 --- a/packages/mosaic/src/machine/types.ts +++ b/packages/mosaic/src/machine/types.ts @@ -143,7 +143,7 @@ export interface StateConfig` cannot express: staying focusable while inert, and behaving like a button on elements that aren't one. +A button with the disabled behavior a native `; +const labeledTrigger = (label: string) => ; const panel = (label: string) => ( @@ -73,7 +73,7 @@ export function Placement() { }} > - + - + - + - + - + - + - + ) { /** * Sliding selected and hover marks, in CSS alone: the selected item and the hovered item each * publish an anchor name, and two pseudo-elements of the tablist follow them through CSS anchor - * positioning — no measuring, no script. Both are the neutral colour at a low opacity, so on the + * positioning — no measuring, no script. Both are the neutral color at a low opacity, so on the * selected item the hover mark stacks on the selected one and the fill deepens rather than * changing hue. Where anchors are unsupported the rules inside `@supports` never apply and the * items keep their own fills. diff --git a/packages/swingset/src/stories/reverification.mdx b/packages/swingset/src/stories/reverification.mdx index b1a9c4bbac1..435a3fe90b4 100644 --- a/packages/swingset/src/stories/reverification.mdx +++ b/packages/swingset/src/stories/reverification.mdx @@ -46,7 +46,7 @@ const step = isRunning && reverification.visible ? 'verify' : 'confirm'; ### When the run ends -The reverification card does not disappear when the action settles. If the user was shown a card during the run, the run ends in a settled state, whether it succeeded, failed, or was cancelled. The controller keeps returning the last card, inert: it is pending and has no `onCancel`. `visible` stays true unless the last thing shown was the pending card. +The reverification card does not disappear when the action settles. If the user was shown a card during the run, the run ends in a settled state, whether it succeeded, failed, or was canceled. The controller keeps returning the last card, inert: it is pending and has no `onCancel`. `visible` stays true unless the last thing shown was the pending card. This lets the parent finish its own work after the action returns, such as refetching or closing a dialog, without the screen falling back to an earlier step in between. The parent leaves the card by unmounting `` or changing its own step when it is done. @@ -59,11 +59,11 @@ The settled card is released by whichever happens first. Call `reset()` when a parent leaves the flow without running the action again and wants a clean state, so the next render does not show a stale card. -### Cancelling +### Canceling `reverification.onCancel` is a request. It is undefined when the user cannot cancel, which is the case while the action is being retried and once the run has settled. Calling it rejects the action with a `reverification_cancelled` error and settles the card. -The parent decides what cancelling means for its own flow. Pass `onClose` to `` to run that instead of `onCancel` when the user presses the Close button on the error card. The parent is then responsible for calling `onCancel` itself, as `Destructive` does when its dialog closes. This lets the parent close or leave the step at the moment of the click, instead of waiting for its own action to unwind. +The parent decides what canceling means for its own flow. Pass `onClose` to `` to run that instead of `onCancel` when the user presses the Close button on the error card. The parent is then responsible for calling `onCancel` itself, as `Destructive` does when its dialog closes. This lets the parent close or leave the step at the moment of the click, instead of waiting for its own action to unwind. ```tsx -A colour per state, far louder than anything you'd ship but told apart at a glance. Move the pointer +A color per state, far louder than anything you'd ship but told apart at a glance. Move the pointer into the region, then onto the bar, then drag it. Only the first of those moves animates, and the reason is structural rather than chromatic: amber → -teal changes the region's own rest colour, so it happens on the scroller, where the transition +teal changes the region's own rest color, so it happens on the scroller, where the transition lives. Pink and violet are the thumb's own states and switch instantly however they're written (see [Styling](#styling)). The example stretches the transition to `0.6s` because at Mosaic's real `0.15s` the fade is over before you've finished moving the pointer in. Every value is an `oklch()` literal, which is what keeps the animated step well defined — the -registered property interpolates between two colours in one space rather than guessing across +registered property interpolates between two colors in one space rather than guessing across notations. ### Shadows instead of the fade @@ -187,7 +187,7 @@ The vars are registered as ``, so they drive position as readily as opac slides out from behind its own edge as it fades in. Clip the root — `overflow: hidden` — so the half that is still offscreen stays there. -Mix the scrim from a theme colour rather than hardcoding black: `--cl-color-foreground` inverts +Mix the scrim from a theme color rather than hardcoding black: `--cl-color-foreground` inverts with the theme, so one declaration reads as a shadow on light and a soft glow on dark, where black would vanish. @@ -209,16 +209,16 @@ These tokens are global, so setting them once retunes every scrolling surface in | `--cl-scroll-fade-range` | `1.5rem` | How far you scroll before the fade reaches full strength. | | `--cl-scrollbar-width` | `8px` | Width of the scrollbar lane. `0px` hides it. | | `--cl-scrollbar-thumb-inset` | `2px` | How far the thumb's paint is held inside that lane. | -| `--cl-scrollbar-thumb` | derived from the palette | Thumb colour once the pointer reaches the region. | -| `--cl-scrollbar-thumb-idle` | derived from the above | Thumb colour while the pointer is elsewhere. | -| `--cl-scrollbar-thumb-hover` | derived from the above | Thumb colour while the pointer is over the thumb. | -| `--cl-scrollbar-thumb-active` | derived from the above | Thumb colour while the thumb is being dragged. | +| `--cl-scrollbar-thumb` | derived from the palette | Thumb color once the pointer reaches the region. | +| `--cl-scrollbar-thumb-idle` | derived from the above | Thumb color while the pointer is elsewhere. | +| `--cl-scrollbar-thumb-hover` | derived from the above | Thumb color while the pointer is over the thumb. | +| `--cl-scrollbar-thumb-active` | derived from the above | Thumb color while the thumb is being dragged. | The two lane sizes are in pixels rather than on the `rem` scale, deliberately: a scrollbar is chrome rather than content, so it should stay the same hairline whether or not the surrounding text scales. The default is a 4px pill in an 8px lane. -The colours are four states running quietest to loudest: `idle` while the pointer is elsewhere, the +The colors are four states running quietest to loudest: `idle` while the pointer is elsewhere, the base once it reaches the region — or the content takes keyboard focus — then `hover` and `active` for the thumb's own two. Each of the three derives from `--cl-scrollbar-thumb` rather than baking its value in, so setting the base re-derives all of them, and any one can still be pinned on its @@ -238,7 +238,7 @@ insets draw the two halves of each cap with different curvature. On a 4px pill t round. Position the surrounding padding instead. Mosaic paints the scrollbar through `::-webkit-scrollbar`, which is what buys a real width and a -thumb colour per interaction state; the standard `scrollbar-color` can express neither, and setting +thumb color per interaction state; the standard `scrollbar-color` can express neither, and setting it would make the engines that _do_ implement the pseudo-elements ignore them. Firefox implements neither and keeps its platform scrollbar. Everything here is gated on `@media (pointer: fine)`, so touch platforms keep the native overlay bar they already draw. The gutter is not gated: it is a @@ -247,7 +247,7 @@ layout decision rather than an appearance one. One consequence worth knowing before you theme: styling the scrollbar takes macOS out of overlay mode, so the bar is always visible and always occupies its lane rather than auto-hiding. That is the cross-platform consistency the tokens exist for, but it is a change from the platform default. To -keep the lane without the bar, take both resting colours transparent: +keep the lane without the bar, take both resting colors transparent: ```css :root { diff --git a/packages/swingset/src/stories/scroll-area.stories.tsx b/packages/swingset/src/stories/scroll-area.stories.tsx index ffbb1f3517c..9e6db6a4b13 100644 --- a/packages/swingset/src/stories/scroll-area.stories.tsx +++ b/packages/swingset/src/stories/scroll-area.stories.tsx @@ -255,7 +255,7 @@ export function Horizontal() { /** * Taking `--cl-scrollbar-thumb-idle` to zero alpha removes the bar entirely until the pointer * reaches the region. `oklch(from … / 0)` rather than `transparent`, which is transparent BLACK and - * drags the fade through dark greys. + * drags the fade through dark grays. */ export function HoverReveal() { const root = stylex.props(scrollAreaRoot); @@ -283,7 +283,7 @@ export function HoverReveal() { } /** - * A colour per state, deliberately louder than anything you'd ship. Only amber → teal animates: + * A color per state, deliberately louder than anything you'd ship. Only amber → teal animates: * it is a change on the SCROLLER, which owns the transition. The duration is stretched well past * Mosaic's own `base` step purely so that one step is impossible to miss. */ diff --git a/packages/swingset/src/stories/tooltip.component.mdx b/packages/swingset/src/stories/tooltip.component.mdx index 94fd7f59699..fb7eb5161a1 100644 --- a/packages/swingset/src/stories/tooltip.component.mdx +++ b/packages/swingset/src/stories/tooltip.component.mdx @@ -52,7 +52,7 @@ A tooltip opens after `delay` (200ms by default) and closes after `closeDelay` ( ### Group -Wrap neighbouring tooltips in `Tooltip.Group` to share one delay: once any tooltip in the group is +Wrap neighboring tooltips in `Tooltip.Group` to share one delay: once any tooltip in the group is open, moving to a sibling trigger shows its label instantly. The group's `delay` replaces each member's own `delay` and `closeDelay`. diff --git a/packages/swingset/src/stories/tooltip.component.stories.tsx b/packages/swingset/src/stories/tooltip.component.stories.tsx index 38362c15f9a..0cbf27f3a33 100644 --- a/packages/swingset/src/stories/tooltip.component.stories.tsx +++ b/packages/swingset/src/stories/tooltip.component.stories.tsx @@ -43,7 +43,7 @@ export function Default() { ); } -const labelledTrigger = (label: string) => ; +const labeledTrigger = (label: string) => ; export function Placement() { return ( @@ -57,19 +57,19 @@ export function Placement() { }} > - + Placed above the trigger. - + Placed below the trigger. - + Placed to the inline start. - + Placed to the inline end. diff --git a/packages/swingset/src/stories/user-button.mdx b/packages/swingset/src/stories/user-button.mdx index 9879dd2dd6d..a1556b85634 100644 --- a/packages/swingset/src/stories/user-button.mdx +++ b/packages/swingset/src/stories/user-button.mdx @@ -95,7 +95,7 @@ fully to match it; see [User only](#user-only). ## Header The header names whatever leads and carries what you can do about it, stacked under it as -full-width labelled buttons. An organization is described by its plan and member count, and carries +full-width labeled buttons. An organization is described by its plan and member count, and carries **Settings** with **Invite**. An account is described by its identifier. Where **Settings** is the only action, as it is for an account, it trails the account inline. diff --git a/packages/ui/src/common/SSOCallback.tsx b/packages/ui/src/common/SSOCallback.tsx index b488d251daf..36319bc260b 100644 --- a/packages/ui/src/common/SSOCallback.tsx +++ b/packages/ui/src/common/SSOCallback.tsx @@ -26,13 +26,13 @@ export const SSOCallbackCard = (props: HandleOAuthCallbackParams | HandleSamlCal const bounceTimeoutRef = React.useRef | undefined>(undefined); React.useEffect(() => { - let cancelled = false; + let canceled = false; if (__internal_setActiveInProgress !== true) { const intent = new URLSearchParams(window.location.search).get('intent'); const reloadResource = intent === 'signIn' || intent === 'signUp' ? intent : undefined; handleRedirectCallback({ ...props, reloadResource }, navigate).catch(e => { - if (cancelled) { + if (canceled) { return; } @@ -46,7 +46,7 @@ export const SSOCallbackCard = (props: HandleOAuthCallbackParams | HandleSamlCal } return () => { - cancelled = true; + canceled = true; clearTimeout(bounceTimeoutRef.current); }; }, [handleError, handleRedirectCallback]); diff --git a/packages/ui/src/components/ConfigureDirectorySync/ConfigureDirectorySyncContext.tsx b/packages/ui/src/components/ConfigureDirectorySync/ConfigureDirectorySyncContext.tsx index 04a17a2cda2..a4464769176 100644 --- a/packages/ui/src/components/ConfigureDirectorySync/ConfigureDirectorySyncContext.tsx +++ b/packages/ui/src/components/ConfigureDirectorySync/ConfigureDirectorySyncContext.tsx @@ -59,7 +59,7 @@ export const ConfigureDirectorySyncProvider = ({ children, }: ConfigureDirectorySyncProviderProps): JSX.Element => { const { data: connections, isLoading: isLoadingConnections } = __internal_useOrganizationEnterpriseConnections(); - // Per-connection Directory Sync is not modelled yet, so the directory hangs + // Per-connection Directory Sync is not modeled yet, so the directory hangs // off the organization's first connection in deterministic order. const connection = sortEnterpriseConnections(connections ?? [])[0]; const enterpriseConnectionId = connection?.id ?? null; diff --git a/packages/ui/src/components/ConfigureDirectorySync/SyncNowRow.tsx b/packages/ui/src/components/ConfigureDirectorySync/SyncNowRow.tsx index 79f30c4b01f..0d1823ac9f9 100644 --- a/packages/ui/src/components/ConfigureDirectorySync/SyncNowRow.tsx +++ b/packages/ui/src/components/ConfigureDirectorySync/SyncNowRow.tsx @@ -15,6 +15,7 @@ const STATUS_COLOR_SCHEME = { succeeded: 'success', running: 'primary', failed: 'danger', + // american-spelling-ignore-next-line: cancelled -- Directory Sync run status the Clerk API returns cancelled: 'warning', } as const; @@ -45,7 +46,7 @@ export const SyncNowRow = ({ status, onSync, onSynced }: SyncNowRowProps): JSX.E try { handleError(err as Error, [], message => setError(typeof message === 'string' ? message : undefined)); } catch { - // handleError rethrows anything it does not recognise, and a network + // handleError rethrows anything it does not recognize, and a network // failure is exactly that. Without this the click would appear to do // nothing at all. setError(t(localizationKeys('configureDirectorySync.testStep.error__syncFailed'))); diff --git a/packages/ui/src/components/ConfigureDirectorySync/__tests__/ConfigureDirectorySyncWizard.test.tsx b/packages/ui/src/components/ConfigureDirectorySync/__tests__/ConfigureDirectorySyncWizard.test.tsx index adcdaeda135..b29e1702a51 100644 --- a/packages/ui/src/components/ConfigureDirectorySync/__tests__/ConfigureDirectorySyncWizard.test.tsx +++ b/packages/ui/src/components/ConfigureDirectorySync/__tests__/ConfigureDirectorySyncWizard.test.tsx @@ -138,7 +138,7 @@ describe('ConfigureDirectorySyncWizard configure step', () => { expect(screen.getByText('Not configured')).toBeInTheDocument(); }); - it('sends the uploaded key and admin email, and does not keep the key afterwards', async () => { + it('sends the uploaded key and admin email, and does not keep the key afterward', async () => { const { wrapper, fixtures } = await createFixtures(withDirectorySyncFixtures); fixtures.clerk.organization?.getEnterpriseConnections.mockResolvedValue([googleConnection]); const existing = googleDirectory(); diff --git a/packages/ui/src/components/ConfigureSSO/ConfigureSSOHeader.tsx b/packages/ui/src/components/ConfigureSSO/ConfigureSSOHeader.tsx index 8d24a2e4c1e..00280feb8b3 100644 --- a/packages/ui/src/components/ConfigureSSO/ConfigureSSOHeader.tsx +++ b/packages/ui/src/components/ConfigureSSO/ConfigureSSOHeader.tsx @@ -16,7 +16,7 @@ export const ConfigureSSOHeader = ({ title }: ConfigureSSOHeaderProps): JSX.Elem const { toggle } = useUnsafeModalContext(); const isModal = Boolean(toggle); - // Breadcrumb membership = labelled steps. `select-provider` has no label, so + // Breadcrumb membership = labeled steps. `select-provider` has no label, so // it is absent from the visual stepper while remaining a real navigable step. const visibleSteps = activeSteps.filter(step => step.label); const currentVisibleIndex = visibleSteps.findIndex(step => activeSteps[currentIndex]?.id === step.id); diff --git a/packages/ui/src/components/ConfigureSSO/__tests__/ConfigureSSO.navigation.test.tsx b/packages/ui/src/components/ConfigureSSO/__tests__/ConfigureSSO.navigation.test.tsx index 603665a5c02..d195e25302c 100644 --- a/packages/ui/src/components/ConfigureSSO/__tests__/ConfigureSSO.navigation.test.tsx +++ b/packages/ui/src/components/ConfigureSSO/__tests__/ConfigureSSO.navigation.test.tsx @@ -281,7 +281,7 @@ describe('ConfigureSSO wizard navigation (integration)', () => { }); }); - // The labelled steps surface in the stepper under their renamed labels, and an + // The labeled steps surface in the stepper under their renamed labels, and an // active connection short-circuits to the (reachable) activate step. it('renders the renamed stepper labels and reaches the activate step', async () => { const { wrapper, fixtures } = await createFixtures(withAdminOrgUser); diff --git a/packages/ui/src/components/ConfigureSSO/hooks/__tests__/useOrganizationEnterpriseConnection.test.tsx b/packages/ui/src/components/ConfigureSSO/hooks/__tests__/useOrganizationEnterpriseConnection.test.tsx index 16a526b0f4b..ec2ee8c908d 100644 --- a/packages/ui/src/components/ConfigureSSO/hooks/__tests__/useOrganizationEnterpriseConnection.test.tsx +++ b/packages/ui/src/components/ConfigureSSO/hooks/__tests__/useOrganizationEnterpriseConnection.test.tsx @@ -81,7 +81,7 @@ vi.mock('@clerk/shared/react', () => ({ totalCount: 0, error: null, // Only report loading when the query is actually enabled — mirrors the - // real disabled-query behaviour (`enabled: false` → not loading). + // real disabled-query behavior (`enabled: false` → not loading). isLoading: params.enabled !== false && testRunsState.isLoading, isFetching: params.enabled !== false && testRunsState.isFetching, isPolling: false, diff --git a/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx b/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx index 97d41c3e3bb..5b4e5643f32 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/__tests__/SelectProviderStep.test.tsx @@ -332,7 +332,7 @@ describe('SelectProviderStep', () => { expect(goNext).not.toHaveBeenCalled(); }); - it('keeps the connection and stays put when the dialog is cancelled', async () => { + it('keeps the connection and stays put when the dialog is canceled', async () => { resetMocks(); contextState.provider = 'saml_okta'; contextState.hasConnection = true; diff --git a/packages/ui/src/components/ProtectCheckModal/index.tsx b/packages/ui/src/components/ProtectCheckModal/index.tsx index 61d8cb43958..8dcf222ab0b 100644 --- a/packages/ui/src/components/ProtectCheckModal/index.tsx +++ b/packages/ui/src/components/ProtectCheckModal/index.tsx @@ -18,8 +18,8 @@ const ProtectCheckModalCard = withCardStateProvider( getResource: () => resource, reload: () => resource.reload(), submitProtectCheck: params => resource.submitProtectCheck(params), - onResolved: (updated, isCancelled) => { - if (!isCancelled() && !updated.protectCheck) { + onResolved: (updated, isCanceled) => { + if (!isCanceled() && !updated.protectCheck) { onResolved(); } return Promise.resolve(); diff --git a/packages/ui/src/components/SignIn/SignInFactorTwoCodeForm.tsx b/packages/ui/src/components/SignIn/SignInFactorTwoCodeForm.tsx index 75b2513288f..51ec834b6c2 100644 --- a/packages/ui/src/components/SignIn/SignInFactorTwoCodeForm.tsx +++ b/packages/ui/src/components/SignIn/SignInFactorTwoCodeForm.tsx @@ -50,7 +50,7 @@ export const SignInFactorTwoCodeForm = (props: SignInFactorTwoCodeFormProps) => // Only show the new device verification notice if the user is new // and no attributes are explicitly used for second factor. - // Retained for backwards compatibility. + // Retained for backward compatibility. const showNewDeviceVerificationNotice = useMemo(() => { const anyAttributeUsedForSecondFactor = Object.values(env.userSettings.attributes).some( attr => attr.used_for_second_factor, diff --git a/packages/ui/src/components/SignIn/SignInFactorTwoEmailLinkCard.tsx b/packages/ui/src/components/SignIn/SignInFactorTwoEmailLinkCard.tsx index 04ef707141e..8111ece9422 100644 --- a/packages/ui/src/components/SignIn/SignInFactorTwoEmailLinkCard.tsx +++ b/packages/ui/src/components/SignIn/SignInFactorTwoEmailLinkCard.tsx @@ -23,7 +23,7 @@ type SignInFactorTwoEmailLinkCardProps = Pick void; }; -// Retained for backwards compatibility. +// Retained for backward compatibility. const isNewDevice = (resource: SignInResource) => resource.clientTrustState === 'new'; export const SignInFactorTwoEmailLinkCard = (props: SignInFactorTwoEmailLinkCardProps) => { diff --git a/packages/ui/src/components/SignIn/SignInProtectCheck.tsx b/packages/ui/src/components/SignIn/SignInProtectCheck.tsx index 03e080e4a70..dbacbc77625 100644 --- a/packages/ui/src/components/SignIn/SignInProtectCheck.tsx +++ b/packages/ui/src/components/SignIn/SignInProtectCheck.tsx @@ -56,8 +56,8 @@ function SignInProtectCheckInternal(): JSX.Element | null { // Routes on the resolved resource. This single path finalizes `complete` from both the normal // success and the `protect_check_already_resolved` reload, so neither strands the user with an // unactivated session. - onResolved: async (updatedSignIn, isCancelled) => { - if (isCancelled()) { + onResolved: async (updatedSignIn, isCanceled) => { + if (isCanceled()) { return; } if (updatedSignIn.status === 'complete' && updatedSignIn.createdSessionId) { diff --git a/packages/ui/src/components/SignIn/SignInStart.tsx b/packages/ui/src/components/SignIn/SignInStart.tsx index 71b14ab903b..25af0488139 100644 --- a/packages/ui/src/components/SignIn/SignInStart.tsx +++ b/packages/ui/src/components/SignIn/SignInStart.tsx @@ -770,7 +770,7 @@ const InstantPasswordRow = ({ }, []); useEffect(() => { - //if the field receives a value, we default to normal behaviour + //if the field receives a value, we default to normal behavior if (field?.value && field.value !== '') { setAutofilled(false); } diff --git a/packages/ui/src/components/SignIn/__tests__/SignInProtectCheck.test.tsx b/packages/ui/src/components/SignIn/__tests__/SignInProtectCheck.test.tsx index ab310b8385b..2c3a69a3248 100644 --- a/packages/ui/src/components/SignIn/__tests__/SignInProtectCheck.test.tsx +++ b/packages/ui/src/components/SignIn/__tests__/SignInProtectCheck.test.tsx @@ -102,7 +102,7 @@ describe('SignInProtectCheck', () => { expect(mockExecute.mock.calls[1][0]).toMatchObject({ token: 'challenge-token-2' }); }); - it('shows a failed hand-off and lets the user retry it, after clearing the challenge cancelled the run', async () => { + it('shows a failed hand-off and lets the user retry it, after clearing the challenge canceled the run', async () => { const { wrapper, fixtures } = await createFixtures(f => { f.startSignInWithProtectCheck(); }); @@ -668,7 +668,7 @@ describe('SignInProtectCheck', () => { it('resumes even when the resolved sign-in no longer carries the transferable marker', async () => { // `SignIn.fromJSON` replaces `firstFactorVerification` wholesale on every write, so the // marker that routed us here is not guaranteed to survive `submitProtectCheck`. The - // component latches it at mount for exactly this case; re-reading it afterwards would + // component latches it at mount for exactly this case; re-reading it afterward would // silently fall back to the broken path. const { wrapper, fixtures } = await createFixtures(f => { f.startSignInWithProtectCheck({ pendingOAuthTransfer: true, status: 'needs_identifier' }); @@ -719,7 +719,7 @@ describe('SignInProtectCheck', () => { }); it('surfaces a message when the resumed continuation fails with a non-Clerk error', async () => { - // `handleError` re-throws what it does not recognise, and the continuation can raise a plain + // `handleError` re-throws what it does not recognize, and the continuation can raise a plain // Error (a transient fetch failure, or a callback that did not complete). That throw escaped // the void-invoked challenge run, leaving the card with no spinner, no message and no retry -- // stranding the user on the very flow this card exists to resume. diff --git a/packages/ui/src/components/SignIn/__tests__/SignInSocialButtons.test.tsx b/packages/ui/src/components/SignIn/__tests__/SignInSocialButtons.test.tsx index 49857e17837..0472663bbc4 100644 --- a/packages/ui/src/components/SignIn/__tests__/SignInSocialButtons.test.tsx +++ b/packages/ui/src/components/SignIn/__tests__/SignInSocialButtons.test.tsx @@ -95,7 +95,7 @@ describe('SignInSocialButtons', () => { }); props.setProps({ oauthFlow: 'popup' } as any); registerOAuthTransport(fixtures.clerk); - fixtures.signIn.authenticateWithRedirect.mockRejectedValue(new Error('cancelled')); + fixtures.signIn.authenticateWithRedirect.mockRejectedValue(new Error('canceled')); const { userEvent } = render( diff --git a/packages/ui/src/components/SignUp/SignUpProtectCheck.tsx b/packages/ui/src/components/SignUp/SignUpProtectCheck.tsx index dbebc37fa31..9991ac3aa99 100644 --- a/packages/ui/src/components/SignUp/SignUpProtectCheck.tsx +++ b/packages/ui/src/components/SignUp/SignUpProtectCheck.tsx @@ -62,8 +62,8 @@ function SignUpProtectCheckInternal({ // `handleComplete`) as well as routing to the next missing-field / verification / chained- // challenge step, so both the normal success and the `protect_check_already_resolved` reload // land correctly. - onResolved: async (updatedSignUp, isCancelled) => { - if (isCancelled()) { + onResolved: async (updatedSignUp, isCanceled) => { + if (isCanceled()) { return; } await completeSignUpFlow({ diff --git a/packages/ui/src/components/SignUp/__tests__/SignUpSocialButtons.test.tsx b/packages/ui/src/components/SignUp/__tests__/SignUpSocialButtons.test.tsx index 0252586c045..3818597df27 100644 --- a/packages/ui/src/components/SignUp/__tests__/SignUpSocialButtons.test.tsx +++ b/packages/ui/src/components/SignUp/__tests__/SignUpSocialButtons.test.tsx @@ -80,7 +80,7 @@ describe('SignUpSocialButtons', () => { }); props.setProps({ oauthFlow: 'popup' } as any); registerOAuthTransport(fixtures.clerk); - fixtures.signUp.authenticateWithRedirect.mockRejectedValue(new Error('cancelled')); + fixtures.signUp.authenticateWithRedirect.mockRejectedValue(new Error('canceled')); const { userEvent } = render( diff --git a/packages/ui/src/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx b/packages/ui/src/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx index 4b6fdf2a630..a8a51dfd214 100644 --- a/packages/ui/src/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx +++ b/packages/ui/src/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx @@ -610,7 +610,7 @@ describe('SubscriptionDetails', () => { expect(nextPaymentElements.length).toBe(2); }); - it('allows cancelling a subscription of a monthly plan', async () => { + it('allows canceling a subscription of a monthly plan', async () => { const { wrapper, fixtures } = await createFixtures(f => { f.withUser({ email_addresses: ['test@clerk.com'] }); f.withBilling(); @@ -1129,7 +1129,7 @@ describe('SubscriptionDetails', () => { expect(getByText('Cancel free trial')).toBeVisible(); }); - it('allows cancelling a free trial with specific dialog text', async () => { + it('allows canceling a free trial with specific dialog text', async () => { const { wrapper, fixtures } = await createFixtures(f => { f.withUser({ email_addresses: ['test@clerk.com'] }); f.withBilling(); diff --git a/packages/ui/src/components/UserButton/SessionActions.tsx b/packages/ui/src/components/UserButton/SessionActions.tsx index 4fde1810678..9cb13fee6b4 100644 --- a/packages/ui/src/components/UserButton/SessionActions.tsx +++ b/packages/ui/src/components/UserButton/SessionActions.tsx @@ -70,7 +70,7 @@ export const SingleSessionActions = (props: SingleSessionActionsProps) => { let itemDescriptors; // We are using different element descriptors for default menu items vs custom items - // to maintain backwards compatibility and avoid breaking existing descriptor usage. + // to maintain backward compatibility and avoid breaking existing descriptor usage. // This check ensures that default items use their specific descriptors, while custom // items use the generic custom item descriptors. if (isDefaultItem) { @@ -222,7 +222,7 @@ export const MultiSessionActions = (props: MultiSessionActionsProps) => { let itemDescriptors; // We are using different element descriptors for default menu items vs custom items - // to maintain backwards compatibility and avoid breaking existing descriptor usage. + // to maintain backward compatibility and avoid breaking existing descriptor usage. // This check ensures that default items use their specific descriptors, while custom // items use the generic custom item descriptors. if (isDefaultItem) { diff --git a/packages/ui/src/components/UserProfile/__tests__/PasskeysSection.test.tsx b/packages/ui/src/components/UserProfile/__tests__/PasskeysSection.test.tsx index 0bf07fee089..0fb3bed6405 100644 --- a/packages/ui/src/components/UserProfile/__tests__/PasskeysSection.test.tsx +++ b/packages/ui/src/components/UserProfile/__tests__/PasskeysSection.test.tsx @@ -101,7 +101,7 @@ describe('PasskeySection', () => { const { wrapper, fixtures } = await createFixtures(withPasskeys); fixtures.clerk.user?.createPasskey.mockRejectedValue( - new ClerkWebAuthnError('Passkey registration was cancelled or timed out.', { + new ClerkWebAuthnError('Passkey registration was canceled or timed out.', { code: 'passkey_registration_cancelled', }), ); @@ -122,7 +122,7 @@ describe('PasskeySection', () => { const { wrapper, fixtures } = await createFixtures(withPasskeys); fixtures.clerk.user?.createPasskey.mockRejectedValueOnce( - new ClerkWebAuthnError('Passkey registration was cancelled or timed out.', { + new ClerkWebAuthnError('Passkey registration was canceled or timed out.', { code: 'passkey_registration_cancelled', }), ); @@ -137,7 +137,7 @@ describe('PasskeySection', () => { const button = getByRole('button', { name: 'Add a passkey' }); await userEvent.click(button); - await waitFor(() => expect(getByTestId('card-error')).toHaveTextContent(/cancelled or timed out/i)); + await waitFor(() => expect(getByTestId('card-error')).toHaveTextContent(/canceled or timed out/i)); fixtures.clerk.user?.createPasskey.mockResolvedValueOnce({} as any); await userEvent.click(button); diff --git a/packages/ui/src/components/devPrompts/EnableOrganizationsPrompt/index.tsx b/packages/ui/src/components/devPrompts/EnableOrganizationsPrompt/index.tsx index 68a2cdecdf1..e35d9923b4c 100644 --- a/packages/ui/src/components/devPrompts/EnableOrganizationsPrompt/index.tsx +++ b/packages/ui/src/components/devPrompts/EnableOrganizationsPrompt/index.tsx @@ -209,7 +209,7 @@ const EnableOrganizationsPromptInternal = ({ setAllowPersonalAccount(value === 'optional')} - labelledBy={radioGroupLabelId} + labeledBy={radioGroupLabelId} > void; children: React.ReactNode; - labelledBy?: string; + labeledBy?: string; }; -const RadioGroup = ({ value, onChange, children, labelledBy }: RadioGroupProps): JSX.Element => { +const RadioGroup = ({ value, onChange, children, labeledBy }: RadioGroupProps): JSX.Element => { const name = useId(); const contextValue = React.useMemo(() => ({ value: { name, value, onChange } }), [name, value, onChange]); @@ -484,7 +484,7 @@ const RadioGroup = ({ value, onChange, children, labelledBy }: RadioGroupProps): direction='col' gap={3} aria-orientation='vertical' - aria-labelledby={labelledBy} + aria-labelledby={labeledBy} > {children} diff --git a/packages/ui/src/elements/FormControl.tsx b/packages/ui/src/elements/FormControl.tsx index 4e655b59fac..ace670a065a 100644 --- a/packages/ui/src/elements/FormControl.tsx +++ b/packages/ui/src/elements/FormControl.tsx @@ -143,7 +143,7 @@ export const FormFeedback = (props: FormFeedbackProps) => { return { elementDescriptor: descriptor, elementId: id ? descriptor?.setId?.(id) : undefined, - // Use legacy pattern for errors (backwards compatible), new pattern for other types + // Use legacy pattern for errors (backward compatible), new pattern for other types id: type === 'error' ? `error-${id}` : `${id}-${type}-feedback`, }; }; diff --git a/packages/ui/src/elements/Select.tsx b/packages/ui/src/elements/Select.tsx index 57c5b3d5247..22e8ca01dc6 100644 --- a/packages/ui/src/elements/Select.tsx +++ b/packages/ui/src/elements/Select.tsx @@ -326,7 +326,7 @@ export const SelectOptionList = (props: SelectOptionListProps) => { const { isOpen, floating, styles, nodeId, context } = popoverCtx; const containerRef = React.useRef(null); const effectiveListboxId = id ?? generatedListboxId; - const effectiveAriaLabelledBy = ariaLabelledBy ?? (ariaLabel ? undefined : triggerId); + const effectiveAriaLabeledBy = ariaLabelledBy ?? (ariaLabel ? undefined : triggerId); const optionId = (index: number) => `${effectiveListboxId}-option-${index}`; React.useEffect(() => { @@ -421,7 +421,7 @@ export const SelectOptionList = (props: SelectOptionListProps) => { direction='col' role='listbox' aria-label={ariaLabel} - aria-labelledby={effectiveAriaLabelledBy} + aria-labelledby={effectiveAriaLabeledBy} aria-activedescendant={comparator ? undefined : activeDescendant} tabIndex={comparator ? undefined : 0} sx={[ diff --git a/packages/ui/src/elements/__tests__/AuthenticationLabels.test.tsx b/packages/ui/src/elements/__tests__/AuthenticationLabels.test.tsx index 4ea97f82ba4..fd980617e15 100644 --- a/packages/ui/src/elements/__tests__/AuthenticationLabels.test.tsx +++ b/packages/ui/src/elements/__tests__/AuthenticationLabels.test.tsx @@ -23,6 +23,7 @@ describe.each(['SignIn', 'SignUp'] as const)('%s accessible labels', component = fixtures.options.localization = { ...frFR, socialButtonsBlockButton: 'Se connecter avec {{provider}}', + // american-spelling-ignore-next-line: utiliser -- French localization socialButtonsBlockButtonManyInView: 'Utiliser {{provider|titleize}}', }; @@ -46,6 +47,7 @@ describe.each(['SignIn', 'SignUp'] as const)('%s accessible labels', component = ); for (const provider of ['Apple', 'Facebook', 'Google']) { + // american-spelling-ignore-next-line: utiliser -- French localization const name = socialButtonsVariant === 'blockButton' ? `Utiliser ${provider}` : `Se connecter avec ${provider}`; const button = screen.getByRole('button', { name, exact: true }); expect(button).toHaveAccessibleName(name); @@ -66,6 +68,7 @@ describe.each(['SignIn', 'SignUp'] as const)('%s accessible labels', component = fixtures.clerk.client.lastAuthenticationStrategy = 'oauth_google'; fixtures.options.localization = { ...frFR, + // american-spelling-ignore-next-line: utilisation -- French localization lastAuthenticationStrategy: 'Dernière utilisation', socialButtonsBlockButton: 'Se connecter avec {{provider}}', }; @@ -91,6 +94,7 @@ describe.each(['SignIn', 'SignUp'] as const)('%s accessible labels', component = ); expect( + // american-spelling-ignore-next-line: utilisation -- French localization screen.getByRole('button', { name: 'Dernière utilisation Se connecter avec Google', exact: true }), ).toBeVisible(); }, diff --git a/packages/ui/src/elements/__tests__/Menu.test.tsx b/packages/ui/src/elements/__tests__/Menu.test.tsx index 37afd2934d4..a93010f3a91 100644 --- a/packages/ui/src/elements/__tests__/Menu.test.tsx +++ b/packages/ui/src/elements/__tests__/Menu.test.tsx @@ -60,7 +60,7 @@ const Harness = ({ items, asPortal, ariaLabel, triggerLabel = 'Open menu' }: Har ); describe('Menu', () => { - describe('Regression — existing click behaviour', () => { + describe('Regression — existing click behavior', () => { it('opens the menu when the trigger is clicked', async () => { const { wrapper } = await createFixtures(); const { getByRole, userEvent, queryByRole } = render( diff --git a/packages/ui/src/hooks/useProtectCheckRunner.ts b/packages/ui/src/hooks/useProtectCheckRunner.ts index 22678724080..e6e6bb382eb 100644 --- a/packages/ui/src/hooks/useProtectCheckRunner.ts +++ b/packages/ui/src/hooks/useProtectCheckRunner.ts @@ -14,9 +14,9 @@ export interface ProtectCheckRunnerParams extends ProtectCheckRunnerR * Continues the flow once the gate clears (or a chained challenge / already-resolved is * detected). Receives the resource to route on (the `submitProtectCheck` result, or the live * resource after a reload) and must finalize (`setActive`) the `complete` case itself. - * `isCancelled` lets the continuation bail if the component unmounted mid-await. + * `isCanceled` lets the continuation bail if the component unmounted mid-await. */ - onResolved: (resource: TResource, isCancelled: () => boolean) => Promise; + onResolved: (resource: TResource, isCanceled: () => boolean) => Promise; onError?: (error: unknown) => void; } @@ -24,7 +24,7 @@ export interface ProtectCheckRunnerState { containerRef: React.MutableRefObject; isRunning: boolean; /** - * Whether the challenge script has signalled (via the `setWidgetVisible` init callback) that + * Whether the challenge script has signaled (via the `setWidgetVisible` init callback) that * it is showing a widget in the container. While true, the widget owns the progress UI — * callers should hide their own spinner and give the container layout space. */ @@ -59,7 +59,7 @@ export function useProtectCheckRunner(params: ProtectCheckRunnerParam const paramsRef = React.useRef(params); paramsRef.current = params; - // `handleError` re-throws what it does not recognise, and this runner awaits caller code that + // `handleError` re-throws what it does not recognize, and this runner awaits caller code that // raises plain errors (a transient fetch failure, an OAuth continuation that did not complete). const reportError = (err: any) => { const { onError } = paramsRef.current; @@ -82,7 +82,7 @@ export function useProtectCheckRunner(params: ProtectCheckRunnerParam const [isWidgetVisible, setIsWidgetVisibleState] = React.useState(false); const [retryNonce, setRetryNonce] = React.useState(0); - // Tracks real unmount, distinct from the per-run `cancelled` flag below. Clearing `protectCheck` + // Tracks real unmount, distinct from the per-run `canceled` flag below. Clearing `protectCheck` // (e.g. an expired-challenge reload that advances the flow) flips the token dependency and // re-runs/cancels the main effect — but that re-run is exactly our cue to route, so the routing // must key on actual unmount, not on the effect's cancel flag. @@ -106,15 +106,15 @@ export function useProtectCheckRunner(params: ProtectCheckRunnerParam } const abortController = new AbortController(); - let cancelled = false; + let canceled = false; // Routing after the gate clears must survive the effect re-run that // clearing `protectCheck` triggers — that re-run is our cue to route, not a // reason to bail. So the onResolved paths below key on REAL unmount, not the - // effect's per-run `cancelled` flag (which the re-run's cleanup sets). + // effect's per-run `canceled` flag (which the re-run's cleanup sets). const isUnmounted = () => !mountedRef.current; const cleanup = () => { - cancelled = true; + canceled = true; abortController.abort(); // Reset the guard so the next mount / token change / retry can re-run; this is what makes // chained challenges work correctly across re-renders. @@ -130,7 +130,7 @@ export function useProtectCheckRunner(params: ProtectCheckRunnerParam // best-effort, so a zombie script from a timed-out, retried, or superseded run can still // call it late — those signals must not flip visibility under the active run. const setWidgetVisible = (visible: boolean): Promise => { - if (cancelled || abortController.signal.aborted || !mountedRef.current) { + if (canceled || abortController.signal.aborted || !mountedRef.current) { return Promise.resolve(); } flushSync(() => setIsWidgetVisibleState(visible)); @@ -174,12 +174,12 @@ export function useProtectCheckRunner(params: ProtectCheckRunnerParam const runId = ++runIdRef.current; // Whether this run still owns the card's error and spinner. Until the gate clears, that ends - // with the run's cancellation. Afterwards the continuation (`onResolved`) is still this run's - // even though clearing the gate cancelled it, so it only stops owning them on unmount or when a - // newer challenge has started a run of its own. Keying the continuation on `cancelled` swallowed + // with the run's cancellation. Afterward the continuation (`onResolved`) is still this run's + // even though clearing the gate canceled it, so it only stops owning them on unmount or when a + // newer challenge has started a run of its own. Keying the continuation on `canceled` swallowed // its failures and left the spinner running with no error. let continuing = false; - const ownsOutcome = () => (continuing ? !isUnmounted() && runIdRef.current === runId : !cancelled); + const ownsOutcome = () => (continuing ? !isUnmounted() && runIdRef.current === runId : !canceled); const runChallenge = async () => { try { diff --git a/packages/ui/src/localization/applyTokensToString.ts b/packages/ui/src/localization/applyTokensToString.ts index 9097b7efe8c..2abc31fc7ae 100644 --- a/packages/ui/src/localization/applyTokensToString.ts +++ b/packages/ui/src/localization/applyTokensToString.ts @@ -25,8 +25,8 @@ export const applyTokensToString = (s: string | undefined, tokens: Tokens): stri if (!s) { return ''; } - const { normalisedString, expressions } = parseTokensFromLocalizedString(s, tokens); - return applyTokenExpressions(normalisedString, expressions, tokens); + const { normalizedString, expressions } = parseTokensFromLocalizedString(s, tokens); + return applyTokenExpressions(normalizedString, expressions, tokens); }; export const useGlobalTokens = (): GlobalTokens => { @@ -49,7 +49,7 @@ export const useGlobalTokens = (): GlobalTokens => { const parseTokensFromLocalizedString = ( s: string, tokens: Tokens, -): { normalisedString: string; expressions: TokenExpression[] } => { +): { normalizedString: string; expressions: TokenExpression[] } => { const matches = (s.match(/{{.+?}}/g) || []).map(m => m.replace(/[{}]/g, '')); const parsedMatches = matches.map(m => m.split('|').map(m => m.trim())); const expressions = parsedMatches @@ -59,13 +59,13 @@ const parseTokensFromLocalizedString = ( modifiers: modifiers.map(m => getModifierWithParams(m)).filter(m => assertKnownModifier(m.modifierName)), })); - let normalisedString = s; + let normalizedString = s; expressions.forEach(({ token }) => { // Marking the position of each token with _++token++_ so we can easily // replace it with its localized value in the next step - normalisedString = normalisedString.replace(/{{.+?}}/, `_++${token}++_`); + normalizedString = normalizedString.replace(/{{.+?}}/, `_++${token}++_`); }); - return { expressions: expressions as TokenExpression[], normalisedString }; + return { expressions: expressions as TokenExpression[], normalizedString }; }; const applyTokenExpressions = (s: string, expressions: TokenExpression[], tokens: Tokens) => { diff --git a/packages/ui/src/primitives/hooks/useFormField.tsx b/packages/ui/src/primitives/hooks/useFormField.tsx index c09d161c9bf..b952f463c2d 100644 --- a/packages/ui/src/primitives/hooks/useFormField.tsx +++ b/packages/ui/src/primitives/hooks/useFormField.tsx @@ -51,7 +51,7 @@ export const FormFieldContextProvider = (props: React.PropsWithChildren [lo, cp, label: hex(cp), })); -const rangeNeighbours = [...new Set(NON_ASCII_IDENT_RANGES.flatMap(([lo, hi]) => [lo - 1, hi + 1]))] +const rangeNeighbors = [...new Set(NON_ASCII_IDENT_RANGES.flatMap(([lo, hi]) => [lo - 1, hi + 1]))] .filter(cp => cp <= 0x10ffff && !inNonAsciiIdentRanges(cp)) .map(cp => ({ cp, label: hex(cp) })); @@ -66,7 +66,7 @@ describe('cssLayerName', () => { expect(isValidCssLayerName(`a${char}`)).toBe(true); }); - it.each(rangeNeighbours)('rejects excluded code point $label as start and continuation', ({ cp }) => { + it.each(rangeNeighbors)('rejects excluded code point $label as start and continuation', ({ cp }) => { const char = String.fromCodePoint(cp); expect(isValidCssLayerName(char)).toBe(false); expect(isValidCssLayerName(`a${char}`)).toBe(false); diff --git a/packages/ui/src/utils/colors/__tests__/legacy.test.ts b/packages/ui/src/utils/colors/__tests__/legacy.test.ts index 199c5e77d1a..2292a025975 100644 --- a/packages/ui/src/utils/colors/__tests__/legacy.test.ts +++ b/packages/ui/src/utils/colors/__tests__/legacy.test.ts @@ -112,9 +112,12 @@ describe('Legacy Colors', () => { expect(colors.toHslaColor('transparent')).toEqual({ h: 0, s: 0, l: 0, a: 0 }); }); + // american-spelling-ignore-next-line: grey -- CSS named color apps pass in it('should handle gray and grey equivalents', () => { const gray = colors.toHslaColor('gray'); + // american-spelling-ignore-next-line: grey -- CSS named color apps pass in const grey = colors.toHslaColor('grey'); + // american-spelling-ignore-next-line: grey -- CSS named color apps pass in expect(gray).toEqual(grey); expect(gray).toEqual({ h: 0, s: 0, l: 50, a: 1 }); }); diff --git a/packages/ui/src/utils/colors/legacy.ts b/packages/ui/src/utils/colors/legacy.ts index b46d4624149..3cd59d2d60b 100644 --- a/packages/ui/src/utils/colors/legacy.ts +++ b/packages/ui/src/utils/colors/legacy.ts @@ -30,6 +30,7 @@ const keywords = { blue: [0, 0, 255, 1], red: [255, 0, 0, 1], green: [0, 128, 0, 1], + // american-spelling-ignore-next-line: grey -- CSS named color apps pass in grey: [128, 128, 128, 1], gray: [128, 128, 128, 1], white: [255, 255, 255, 1], diff --git a/packages/ui/src/utils/errorHandler.ts b/packages/ui/src/utils/errorHandler.ts index 7d31438faad..55f269bff7b 100644 --- a/packages/ui/src/utils/errorHandler.ts +++ b/packages/ui/src/utils/errorHandler.ts @@ -76,7 +76,7 @@ export const handleError: HandleError = (err, fieldStates, setGlobalError) => { } if (isClerkRuntimeError(err) && err.code === 'reverification_cancelled') { - // Don't log or display an error for cancelled reverification, the user simply closed the modal. + // Don't log or display an error for canceled reverification, the user simply closed the modal. return; } diff --git a/packages/upgrade/src/cli.js b/packages/upgrade/src/cli.js index 7901129ed60..a9c8ea296bb 100644 --- a/packages/upgrade/src/cli.js +++ b/packages/upgrade/src/cli.js @@ -231,7 +231,7 @@ async function main() { }); if (isInteractive && !(await promptConfirm('Ready to upgrade?', true))) { - renderError('Upgrade cancelled. Exiting...'); + renderError('Upgrade canceled. Exiting...'); process.exit(0); } diff --git a/packages/upgrade/src/guide-generators/core-2/retheme/index.js b/packages/upgrade/src/guide-generators/core-2/retheme/index.js index 91a7898d860..e3866d49108 100644 --- a/packages/upgrade/src/guide-generators/core-2/retheme/index.js +++ b/packages/upgrade/src/guide-generators/core-2/retheme/index.js @@ -16,7 +16,7 @@ async function generate() { title: 'Changes to default variables', content: `\nThe default values of some [appearance variables](/docs/components/customization/variables) have changed which may impact your UI (if you are not already overriding them). - - The default \`colorPrimary\` value changed from \`#103FEF\` to \`#2F3037\`. As the new color is a dark grey, the \`colorPrimary\` of the dark theme was changed to \`#FFFFFF\`. + - The default \`colorPrimary\` value changed from \`#103FEF\` to \`#2F3037\`. As the new color is a dark gray, the \`colorPrimary\` of the dark theme was changed to \`#FFFFFF\`. - The default \`fontSize\` value changed from \`1rem\` to \`0.8125rem\` - The default \`fontWeight\` values changed from \`{ normal: 400, medium: 500, bold: 600 }\` to \`{ normal: 400, medium: 500, semibold: 600, bold: 700 }\`.\n`, }; diff --git a/scripts/american-spelling.mjs b/scripts/american-spelling.mjs new file mode 100644 index 00000000000..5def736396b --- /dev/null +++ b/scripts/american-spelling.mjs @@ -0,0 +1,619 @@ +// A twin of this script lives in clerk/clerk at `scripts/lint/american-spelling.mjs`. +// Make every change to the denylist or the logic in both copies. +// +// Keep the repo in American English: `color`, not `colour`; `canceled`, not +// `cancelled`. Clerk's product, APIs and SDKs spell things the American way, so +// prose that doesn't reads as a second voice, and review threads end up +// settling the same dispute one word at a time. See docs/CONTRIBUTING.md. +// +// This is a denylist of known British spellings, not a spellchecker: it flags +// only the words below, in prose and code alike (identifiers are split on +// camelCase and snake_case, so `isCancelled` is caught too). +// Runs in `pnpm lint` (CI on every PR). `--fix` rewrites every offender in place. +import { execFileSync } from 'node:child_process'; +import { readFileSync, writeFileSync } from 'node:fs'; + +// -ise/-isation → -ize/-ization. Roots only; the suffixes below are appended. +// Words that end in -ise in American English too (advise, promise, exercise, +// enterprise, surprise, ...) are not built from any of these roots. +const IZE_ROOTS = [ + 'agon', + 'amort', + 'anonym', + 'antagon', + 'apolog', + 'atom', + 'author', + 'canonical', + 'capital', + 'categor', + 'central', + 'character', + 'colon', + 'commercial', + 'computer', + 'conceptual', + 'container', + 'contextual', + 'critic', + 'custom', + 'decentral', + 'deserial', + 'desensit', + 'destabil', + 'digit', + 'dramat', + 'econom', + 'emphas', + 'empath', + 'energ', + 'equal', + 'evangel', + 'external', + 'familiar', + 'fantas', + 'final', + 'formal', + 'fossil', + 'general', + 'global', + 'harmon', + 'hospital', + 'human', + 'hybrid', + 'ideal', + 'immun', + 'incentiv', + 'individual', + 'industrial', + 'initial', + 'institutional', + 'internal', + 'international', + 'italic', + 'item', + 'jeopard', + 'legal', + 'legitim', + 'lexical', + 'local', + 'magnet', + 'marginal', + 'material', + 'maxim', + 'mechan', + 'memor', + 'metabol', + 'miniatur', + 'minim', + 'mobil', + 'modern', + 'modular', + 'monet', + 'monopol', + 'national', + 'natural', + 'neutral', + 'normal', + 'operational', + 'optim', + 'organ', + 'ostrac', + 'oxid', + 'parameter', + 'parametr', + 'patron', + 'penal', + 'personal', + 'plural', + 'polar', + 'popular', + 'pressur', + 'priorit', + 'privat', + 'product', + 'pseudonym', + 'public', + 'radical', + 'random', + 'rational', + 'real', + 'reauthor', + 'recogn', + 'regional', + 'regular', + 'reinitial', + 'reorgan', + 'revital', + 'revolution', + 'sanit', + 'scrutin', + 'sensit', + 'serial', + 'social', + 'special', + 'stabil', + 'standard', + 'steril', + 'stigmat', + 'styl', + 'subsid', + 'summar', + 'symbol', + 'sympath', + 'synchron', + 'synthes', + 'systemat', + 'tantal', + 'temporal', + 'terror', + 'theor', + 'token', + 'traumat', + 'trivial', + 'unauthor', + 'unorgan', + 'unrecogn', + 'urban', + 'util', + 'vandal', + 'vector', + 'verbal', + 'victim', + 'virtual', + 'visual', +]; +const IZE_SUFFIXES = ['e', 'es', 'ed', 'ing', 'er', 'ers', 'ation', 'ations', 'ational', 'able']; + +// -yse → -yze. +const YZE_ROOTS = ['anal', 'catal', 'dial', 'electrol', 'hydrol', 'paral']; +// `analyses` is left out: it's also the American plural of `analysis`. +const YZE_SUFFIXES = ['e', 'ed', 'ing', 'er', 'ers']; + +// -our → -or. +const OR_ROOTS = [ + 'arb', + 'ard', + 'arm', + 'behavi', + 'cand', + 'clam', + 'col', + 'endeav', + 'fav', + 'ferv', + 'flav', + 'harb', + 'hon', + 'hum', + 'lab', + 'neighb', + 'od', + 'parl', + 'rig', + 'rum', + 'sav', + 'splend', + 'succ', + 'tum', + 'val', + 'vap', + 'vig', +]; +const OR_SUFFIXES = [ + 'our', + 'ours', + 'oured', + 'ouring', + 'ourful', + 'ourless', + 'ourable', + 'ourably', + 'ourite', + 'ourites', + 'oural', + 'ourally', + 'ourist', + 'ourhood', + 'oury', +]; + +// -re → -er. +const ER_ROOTS = ['calib', 'cent', 'fib', 'lit', 'lust', 'meag', 'met', 'sab', 'somb', 'spect', 'theat']; +const ER_SUFFIXES = [ + ['re', 'er'], + ['res', 'ers'], + ['red', 'ered'], + ['ring', 'ering'], +]; + +// Doubled final l before a suffix → single l. +const L_ROOTS = [ + 'barrel', + 'bevel', + 'cancel', + 'channel', + 'chisel', + 'counsel', + 'dial', + 'duel', + 'enamel', + 'equal', + 'fuel', + 'funnel', + 'gravel', + 'grovel', + 'initial', + 'jewel', + 'label', + 'level', + 'libel', + 'marshal', + 'marvel', + 'medal', + 'model', + 'panel', + 'parcel', + 'pedal', + 'pencil', + 'quarrel', + 'refuel', + 'remodel', + 'revel', + 'rival', + 'shovel', + 'signal', + 'snorkel', + 'spiral', + 'stencil', + 'swivel', + 'total', + 'towel', + 'travel', + 'trial', + 'tunnel', + 'unravel', + 'yodel', +]; +const L_SUFFIXES = ['led', 'ling', 'ler', 'lers', 'lor', 'lors', 'lous', 'lously']; + +// Everything else, word for word. +const WORDS = { + acknowledgement: 'acknowledgment', + acknowledgements: 'acknowledgments', + aeroplane: 'airplane', + aeroplanes: 'airplanes', + afterwards: 'afterward', + ageing: 'aging', + aluminium: 'aluminum', + amidst: 'amid', + amongst: 'among', + analogue: 'analog', + analogues: 'analogs', + appal: 'appall', + artefact: 'artifact', + artefacts: 'artifacts', + backwards: 'backward', + benefitted: 'benefited', + benefitting: 'benefiting', + catalogue: 'catalog', + catalogued: 'cataloged', + catalogues: 'catalogs', + cataloguing: 'cataloging', + cheque: 'check', + cheques: 'checks', + cosy: 'cozy', + defence: 'defense', + defences: 'defenses', + despatch: 'dispatch', + distil: 'distill', + downwards: 'downward', + dreamt: 'dreamed', + enquire: 'inquire', + enquired: 'inquired', + enquires: 'inquires', + enquiries: 'inquiries', + enquiring: 'inquiring', + enquiry: 'inquiry', + enrol: 'enroll', + enrolment: 'enrollment', + enrolments: 'enrollments', + enrols: 'enrolls', + focussed: 'focused', + focusses: 'focuses', + focussing: 'focusing', + fulfil: 'fulfill', + fulfilment: 'fulfillment', + fulfils: 'fulfills', + grey: 'gray', + greyed: 'grayed', + greyer: 'grayer', + greyish: 'grayish', + greys: 'grays', + instalment: 'installment', + instalments: 'installments', + instil: 'instill', + instils: 'instills', + jewellery: 'jewelry', + judgement: 'judgment', + judgements: 'judgments', + kerb: 'curb', + learnt: 'learned', + licence: 'license', + licences: 'licenses', + licenced: 'licensed', + manoeuvre: 'maneuver', + manoeuvres: 'maneuvers', + maths: 'math', + mould: 'mold', + offence: 'offense', + offences: 'offenses', + onwards: 'onward', + orientated: 'oriented', + plough: 'plow', + practise: 'practice', + practised: 'practiced', + practises: 'practices', + practising: 'practicing', + pretence: 'pretense', + programme: 'program', + programmes: 'programs', + sceptic: 'skeptic', + sceptical: 'skeptical', + scepticism: 'skepticism', + skilful: 'skillful', + skilfully: 'skillfully', + speciality: 'specialty', + spelt: 'spelled', + storey: 'story', + storeys: 'stories', + towards: 'toward', + tranquillity: 'tranquility', + tyre: 'tire', + tyres: 'tires', + upwards: 'upward', + whilst: 'while', + wilful: 'willful', + wilfully: 'willfully', + woollen: 'woolen', +}; + +const DENYLIST = new Map(Object.entries(WORDS)); +for (const root of IZE_ROOTS) { + for (const suffix of IZE_SUFFIXES) { + DENYLIST.set(`${root}is${suffix}`, `${root}iz${suffix}`); + } +} +for (const root of YZE_ROOTS) { + for (const suffix of YZE_SUFFIXES) { + DENYLIST.set(`${root}ys${suffix}`, `${root}yz${suffix}`); + } +} +for (const root of OR_ROOTS) { + for (const suffix of OR_SUFFIXES) { + DENYLIST.set(`${root}${suffix}`, `${root}${suffix.replace('our', 'or')}`); + } +} +for (const root of ER_ROOTS) { + for (const [british, american] of ER_SUFFIXES) { + DENYLIST.set(`${root}${british}`, `${root}${american}`); + } +} +for (const root of L_ROOTS) { + for (const suffix of L_SUFFIXES) { + DENYLIST.set(`${root}${suffix}`, `${root}${suffix.slice(1)}`); + } +} + +// Names we don't own. Each needs a reason; the upstream ones get fixed at the +// source, then come off this list. +const EXCEPTIONS = new Map([ + // The ARIA spec's `aria-labelledby`, camelCased. The attribute itself never + // matches: `labelledby` is one word, not `labelled` + `by`. + ['ariaLabelledBy', 'ARIA spec'], + // Public API that apps and older SDK versions depend on. Each gets an + // American-spelled replacement in a major version, then comes off this list. + ['isReverificationCancelledError', 'deprecated alias of isReverificationCanceledError'], + ['reverification_cancelled', 'ClerkRuntimeError code apps compare against'], + ['passkey_registration_cancelled', 'ClerkRuntimeError code and localization key'], + ['passkey_retrieval_cancelled', 'ClerkRuntimeError code and localization key'], + ['SIGN_IN_CANCELLED', 'error code the @clerk/expo-google-signin native modules reject with'], + ['afterVerificationCancelled', 'prop older @clerk/shared versions pass to @clerk/ui'], + ['syncStatus__cancelled', 'localization key apps override by name'], +]); + +// Generated, released, or not American English on purpose: never respell these. +const GENERATED = [ + /(^|\/)pnpm-lock\.yaml$/, // npm package names (`@img/colour`) + /(^|\/)CHANGELOG\.md$/, // released notes, written by changesets + // Translations: en-GB is British on purpose, and the rest aren't English + // (`Organisation` is German and French). + /^packages\/localizations\/src\/(?!en-US\.ts$)[a-z]{2}-[A-Z]{2}\.ts$/, +]; + +// This file and its test list the British forms on purpose. +const SELF = new Set(['scripts/american-spelling.mjs', 'scripts/american-spelling.test.mjs']); + +const TEXT_RE = /\.(md|mdx|ts|tsx|js|jsx|mjs|cjs|mts|cts|json|jsonc|ya?ml|css|html|txt|sh)$/i; +// Addresses, not words. Respelling one breaks whatever it points at, so these +// are blanked out before matching: +const ADDRESS_RES = [ + // Anything with a slash: URLs, root-relative and relative paths, link targets, + // `url(...)` values, and scoped import paths. Prose like `colour/flavour` is + // skipped too, which is the price of never touching a path. + /[^\s"'`()<>[\]{},;]*\/[^\s"'`()<>[\]{},;]*/g, + // Markdown link targets without a slash (`[notes](colour.pdf)`, `[see](#colour)`). + /(?<=\]\()[^)\s]+/g, + // Bare file names (`colour-logo.svg`). + /[\w.-]+\.(?:avif|css|gif|ico|jpe?g|json|m?jsx?|mdx?|png|svg|tsx?|webp|woff2?)\b/g, + // In-page anchors (`href="#colour"`) and bare package names (`from 'colour'`). + /(?<=\b(?:href|src)=["'])[^"']+/g, + /(?<=\b(?:from|import|import\(|require\()\s*["'])[^"']+/g, + // Markdown reference definitions (`[ref]: #colour`). + /(?<=^\s*\[[^\]]+\]:\s*)\S+/g, +]; +const IDENTIFIER_RE = /[A-Za-z0-9_'-]+/g; +const WORD_RE = /[A-Za-z]+/g; +// camelCase and PascalCase boundaries, including acronyms (`URLParser` → URL, Parser). +const CAMEL_RE = /[A-Z]+(?![a-z])|[A-Z]?[a-z]+/g; + +function matchCase(original, replacement) { + if (original === original.toUpperCase() && original.length > 1) { + return replacement.toUpperCase(); + } + if (original[0] === original[0].toUpperCase()) { + return replacement[0].toUpperCase() + replacement.slice(1); + } + return replacement; +} + +function exceptionAt(line, index) { + for (const match of line.matchAll(IDENTIFIER_RE)) { + if (index < match.index || index >= match.index + match[0].length) { + continue; + } + return EXCEPTIONS.has(match[0].replace(/^['-]+|['-]+$/g, '')); + } + return false; +} + +// Returns [{ column, found, replacement }] for one line. Addresses are blanked +// out first (same length, so columns hold). +// A marker comment lets the words it names through on the next non-blank line +// only, for spelling we don't own: a direct quotation or a published title. Any +// comment syntax works, word matching ignores case, and a `-- reason` is ignored: +// {/* american-spelling-ignore-next-line: Towards -- NIST paper title */} +// The colon is required, so prose that only mentions the marker isn't one. +const MARKER_RE = /american-spelling-ignore-next-line:([A-Za-z,\s]*)(?:--[^*}>\n]*)?/gi; + +// Returns the lowercased words a marker on this line allows, or null for no marker. +export function markerWords(line) { + const match = [...line.matchAll(MARKER_RE)][0]; + if (!match) { + return null; + } + return new Set( + (match[1] ?? '') + .split(/[\s,]+/) + .filter(Boolean) + .map(word => word.toLowerCase()), + ); +} + +const blank = text => ' '.repeat(text.length); + +export function findInLine(line) { + const masked = [MARKER_RE, ...ADDRESS_RES].reduce((text, re) => text.replace(re, blank), line); + const found = []; + for (const word of masked.matchAll(WORD_RE)) { + for (const part of word[0].matchAll(CAMEL_RE)) { + const american = DENYLIST.get(part[0].toLowerCase()); + if (!american) { + continue; + } + const column = word.index + part.index; + if (exceptionAt(line, column)) { + continue; + } + found.push({ column, found: part[0], replacement: matchCase(part[0], american) }); + } + } + return found; +} + +// Returns the hits each line still owes after markers, plus any marker that +// names no words or names a word its line doesn't use (so markers can't go stale). +export function checkLines(lines) { + const hits = []; + const problems = []; + let previous = -1; // the last non-blank line, which is where a marker for this line sits + lines.forEach((line, i) => { + if (!line.trim()) { + return; + } + const markerLine = previous; + previous = i; + const allowed = markerLine >= 0 ? markerWords(lines[markerLine]) : null; + const used = new Set(); + for (const hit of findInLine(line)) { + const word = hit.found.toLowerCase(); + if (allowed?.has(word)) { + used.add(word); + } else { + hits.push({ line: i, ...hit }); + } + } + if (allowed?.size === 0) { + problems.push({ line: markerLine, message: 'marker names no words; list the ones to allow' }); + } + for (const word of allowed ?? []) { + if (!used.has(word)) { + problems.push({ line: markerLine, message: `marker allows "${word}", but the next line doesn't use it` }); + } + } + }); + // A marker on the last non-blank line has nothing to apply to. + if (previous >= 0 && markerWords(lines[previous])) { + problems.push({ line: previous, message: 'marker has no line after it to apply to' }); + } + return { hits, problems }; +} + +if (process.argv[1]?.endsWith('american-spelling.mjs')) { + const fix = process.argv.includes('--fix'); + const files = execFileSync('git', ['ls-files'], { encoding: 'utf8' }) + .split('\n') + .filter(file => TEXT_RE.test(file) && !SELF.has(file) && !GENERATED.some(re => re.test(file))); + + const offenders = []; + const markerProblems = []; + for (const file of files) { + let source; + try { + source = readFileSync(file, 'utf8'); + } catch { + continue; // deleted in the working tree but still in the index + } + const lines = source.split('\n'); + const { hits, problems } = checkLines(lines); + for (const problem of problems) { + markerProblems.push(`${file}:${problem.line + 1} ${problem.message}`); + } + for (const hit of hits) { + offenders.push(`${file}:${hit.line + 1}:${hit.column + 1} ${hit.found} → ${hit.replacement}`); + } + if (!fix || hits.length === 0) { + continue; + } + // Right to left, so earlier columns on a line stay valid. + for (const hit of hits.toReversed()) { + const line = lines[hit.line]; + lines[hit.line] = line.slice(0, hit.column) + hit.replacement + line.slice(hit.column + hit.found.length); + } + writeFileSync(file, lines.join('\n')); + } + + if (markerProblems.length > 0) { + console.error('Fix these american-spelling-ignore-next-line markers:'); + for (const problem of markerProblems) { + console.error(` ${problem}`); + } + process.exitCode = 1; + } + + if (offenders.length > 0 && !fix) { + console.error('Use American English spelling — see docs/CONTRIBUTING.md.'); + console.error('Run `pnpm lint:american-spelling --fix` to rewrite these, then review the diff.'); + console.error('Offending words:'); + for (const offender of offenders) { + console.error(` ${offender}`); + } + process.exit(1); + } + + if (fix) { + console.log(`Rewrote ${offenders.length} British spellings (${files.length} files scanned).`); + } else if (!process.exitCode) { + console.log(`No British spellings (${files.length} files scanned).`); + } +} diff --git a/scripts/american-spelling.test.mjs b/scripts/american-spelling.test.mjs new file mode 100644 index 00000000000..eb1928826b5 --- /dev/null +++ b/scripts/american-spelling.test.mjs @@ -0,0 +1,117 @@ +// A twin of this test lives in clerk/clerk at `scripts/lint/american-spelling.test.ts`. +// Make every change to the cases in both copies. +import { describe, expect, test } from 'vitest'; + +import { checkLines, findInLine, markerWords } from './american-spelling.mjs'; + +const words = line => findInLine(line).map(hit => hit.replacement); + +describe('findInLine', () => { + test('flags British spellings in prose and keeps their case', () => { + expect(words('Customise the colour, then CANCELLED it.')).toEqual(['Customize', 'color', 'CANCELED']); + }); + + test('flags British spellings inside camelCase and snake_case identifiers', () => { + expect(words('let isCancelled = normalise_colour(x)')).toEqual(['Canceled', 'normalize', 'color']); + }); + + test('leaves American words that look British alone', () => { + expect(words('advise the enterprise on cancellation and analyses')).toEqual([]); + }); + + test('leaves names owned elsewhere alone', () => { + expect( + words( + "ariaLabelledBy SIGN_IN_CANCELLED isReverificationCancelledError 'reverification_cancelled' aria-labelledby", + ), + ).toEqual([]); + }); + + test.each([ + ['an absolute URL', 'See https://example.com/colour-guide.'], + ['a Markdown link target', 'See [the guide](/docs/customise).'], + ['a Markdown link target without a slash', 'See [the notes](colour.pdf).'], + ['a Markdown reference definition', '[guide]: #colour'], + ['a root-relative string', "const logo = '/images/colour-logo.svg'"], + ['a relative path', 'Open ./centre/index.ts first.'], + ['a CSS url()', 'background: url(/images/colour.png);'], + ['a bare file name', 'Rename colour-logo.svg later.'], + ['an in-page href', ''], + ['a static import', "import x from 'colour'"], + ['a dynamic import', "await import('@vendor/customise')"], + ['a require', "require('colour')"], + ])('leaves %s alone', (_, line) => { + expect(words(line)).toEqual([]); + }); + + test('still flags prose next to an address', () => { + expect(words('The [colour picker](/docs/colour) sets the colour.')).toEqual(['color', 'color']); + }); +}); + +describe('markerWords', () => { + test.each([ + ['an MDX comment', '{/* american-spelling-ignore-next-line: Towards */}'], + ['an HTML comment', ''], + ['a line comment', '// american-spelling-ignore-next-line: Towards'], + ['a reason after --', '{/* american-spelling-ignore-next-line: Towards -- NIST paper title */}'], + ['a different case', '{/* American-Spelling-Ignore-Next-Line: TOWARDS */}'], + ])('reads the allowed words from %s', (_, line) => { + expect(markerWords(line)).toEqual(new Set(['towards'])); + }); + + test('reads several words', () => { + expect(markerWords('// american-spelling-ignore-next-line: colour, behaviour')).toEqual( + new Set(['colour', 'behaviour']), + ); + }); + + test('returns null without a marker', () => { + expect(markerWords('// just a comment')).toBeNull(); + }); + + test('returns null for a mention of the marker without its colon', () => { + expect(markerWords('Add an `american-spelling-ignore-next-line` comment above the quote.')).toBeNull(); + }); +}); + +describe('checkLines', () => { + const owed = lines => checkLines(lines).hits.map(hit => `${hit.line}:${hit.found}`); + const problems = lines => checkLines(lines).problems.map(problem => `${problem.line}:${problem.message}`); + + test('lets only the named words through on the next non-blank line, ignoring case', () => { + const lines = ['{/* american-spelling-ignore-next-line: towards */}', '', '"Towards a standard" sets the colour.']; + expect(owed(lines)).toEqual(['2:colour']); + expect(problems(lines)).toEqual([]); + }); + + test('does not reach past the next non-blank line', () => { + expect(owed(['// american-spelling-ignore-next-line: colour', 'const colour = 1', 'const colour = 2'])).toEqual([ + '2:colour', + ]); + }); + + test('does not flag the words named in the marker itself', () => { + expect(owed(['', 'colour'])).toEqual( + [], + ); + }); + + test('reports a marker that names no words', () => { + expect(problems(['// american-spelling-ignore-next-line:', 'colour'])).toEqual([ + '0:marker names no words; list the ones to allow', + ]); + }); + + test('reports a marker with no line after it', () => { + expect(problems(['const color = 1', '// american-spelling-ignore-next-line: colour', ''])).toEqual([ + '1:marker has no line after it to apply to', + ]); + }); + + test('reports a marker word the next line does not use, so markers cannot go stale', () => { + expect(problems(['// american-spelling-ignore-next-line: colour', 'const color = 1'])).toEqual([ + `0:marker allows "colour", but the next line doesn't use it`, + ]); + }); +}); diff --git a/scripts/lint.mjs b/scripts/lint.mjs index c2441d8f3f5..1b4bad6d613 100644 --- a/scripts/lint.mjs +++ b/scripts/lint.mjs @@ -9,11 +9,12 @@ $.env = { $.stdio = 'inherit'; $.verbose = !!process.env.VERBOSE; -const [github, integration, scripts, dashboardLinks] = await Promise.allSettled([ +const [github, integration, scripts, dashboardLinks, americanSpelling] = await Promise.allSettled([ $`echo "SKIPPING: pnpm eslint .github/workflows"`, $`pnpm eslint integration`, $`pnpm eslint scripts`, $`pnpm lint:dashboard-links`, + $`pnpm lint:american-spelling`, ]); let packages; @@ -39,6 +40,7 @@ logLintResult(github.status, 'GitHub Actions', 'pnpm eslint .github/workflows'); logLintResult(integration.status, 'Integration directory', 'pnpm eslint integration'); logLintResult(scripts.status, 'Scripts directory', 'pnpm eslint scripts'); logLintResult(dashboardLinks.status, 'Dashboard links', 'pnpm lint:dashboard-links'); +logLintResult(americanSpelling.status, 'American spelling', 'pnpm lint:american-spelling'); if (packages?.exitCode) { console.log(''); @@ -46,6 +48,11 @@ if (packages?.exitCode) { console.log(`Run '${chalk.bold('pnpm turbo lint')}' to reproduce the error`); } -if (integration.status === 'rejected' || dashboardLinks.status === 'rejected' || packages?.exitCode) { +if ( + integration.status === 'rejected' || + dashboardLinks.status === 'rejected' || + americanSpelling.status === 'rejected' || + packages?.exitCode +) { process.exit(1); } From 35ee4deb7d67d0e637bef5be5652bd796d32d718 Mon Sep 17 00:00:00 2001 From: Michael Novotny Date: Wed, 7 Oct 2026 16:41:11 -0500 Subject: [PATCH 3/4] test(mosaic): use American spelling in new passkey feature tests Co-Authored-By: Claude Opus 5.5 --- .../user-profile-passkeys-cleanup.feature.test.tsx | 8 ++++---- .../user-profile-passkeys-lifecycle.feature.test.tsx | 2 +- .../user-profile-passkeys-section.feature.test.tsx | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/mosaic/src/features/user-profile/__tests__/user-profile-passkeys-cleanup.feature.test.tsx b/packages/mosaic/src/features/user-profile/__tests__/user-profile-passkeys-cleanup.feature.test.tsx index 0cde55dc3f4..0d0e0a428a9 100644 --- a/packages/mosaic/src/features/user-profile/__tests__/user-profile-passkeys-cleanup.feature.test.tsx +++ b/packages/mosaic/src/features/user-profile/__tests__/user-profile-passkeys-cleanup.feature.test.tsx @@ -10,7 +10,7 @@ import { UserProfilePasskeysSection } from '../user-profile-passkeys-section/use afterEach(() => vi.restoreAllMocks()); -function cancelledRegistration() { +function canceledRegistration() { vi.spyOn(navigator, 'webdriver', 'get').mockReturnValue(false); const environment = fapiEnvironment(); environment.user_settings.attributes.passkey.enabled = true; @@ -18,7 +18,7 @@ function cancelledRegistration() { environment, client: fapiClient([fapiSession({ id: 'sess_1', user: fapiUser({ id: 'user_1', passkeys: [] }) })]), }); - vi.spyOn(navigator.credentials, 'create').mockRejectedValue(new DOMException('Cancelled', 'NotAllowedError')); + vi.spyOn(navigator.credentials, 'create').mockRejectedValue(new DOMException('Canceled', 'NotAllowedError')); return state; } @@ -31,7 +31,7 @@ async function pendingResourceStatus() { } it('removes the pending registration after cancellation, rather than merely hiding its row', async () => { - cancelledRegistration(); + canceledRegistration(); await renderWithClerk(); const cleanup = holdRequests('post', '/v1/me/passkeys/passkey_1'); await userEvent.setup().click(screen.getByRole('button', { name: 'Add passkey' })); @@ -43,7 +43,7 @@ it('removes the pending registration after cancellation, rather than merely hidi }); it('retains the localized cancellation error when pending cleanup fails', async () => { - cancelledRegistration(); + canceledRegistration(); await renderWithClerk( { it('uses localized coded cancellation errors', async () => { serveAccounts(); - authenticator().mockRejectedValueOnce(new DOMException('Cancelled', 'NotAllowedError')); + authenticator().mockRejectedValueOnce(new DOMException('Canceled', 'NotAllowedError')); await renderWithClerk( { it('shows an authenticator cancellation and allows another attempt', async () => { servePasskeys([]); - fakeAuthenticator().mockRejectedValueOnce(new DOMException('Cancelled', 'NotAllowedError')); + fakeAuthenticator().mockRejectedValueOnce(new DOMException('Canceled', 'NotAllowedError')); await renderWithClerk(); const user = userEvent.setup(); await user.click(screen.getByRole('button', { name: 'Add passkey' })); From e54b657e5f13dab512739d5e7a42ecaab6401f8a Mon Sep 17 00:00:00 2001 From: Michael Novotny Date: Thu, 8 Oct 2026 11:56:46 -0500 Subject: [PATCH 4/4] ci(repo): mark the new cancelled() step condition for the American spelling check Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee80c3620da..7ff6611df33 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -363,6 +363,7 @@ jobs: run: pnpm --filter @clerk/mosaic test:feature - name: Upload failure artifacts + # american-spelling-ignore-next-line: cancelled -- GitHub Actions status function if: ${{ cancelled() || failure() }} uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: