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-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/.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/.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 5f1ca274d8c..17ff234ad79 100644 --- a/.claude/skills/mosaic/SKILL.md +++ b/.claude/skills/mosaic/SKILL.md @@ -86,7 +86,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/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee010ea28b4..7ff6611df33 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 @@ -360,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: @@ -586,6 +590,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: | @@ -595,6 +600,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 0454bdd60b6..06541144f1b 100644 --- a/.github/workflows/e2e-staging.yml +++ b/.github/workflows/e2e-staging.yml @@ -35,7 +35,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 @@ -304,6 +304,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: @@ -318,6 +319,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 95f774f1a1d..14988078e15 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/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/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 314c28a7ccf..81beda14c99 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({ @@ -4572,7 +4572,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; @@ -4587,7 +4587,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 e11ec6a2618..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, @@ -22,10 +22,10 @@ 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'; +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 f42b1adcf76..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,12 +160,12 @@ 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. * - * - `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/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 23ef61ac524..c73808c716f 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 7e862ea758e..5a841b8fa45 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -369,7 +369,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', @@ -2189,8 +2189,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/docs/migration.md b/packages/mosaic/docs/migration.md index ac084d19997..dc86c6ef494 100644 --- a/packages/mosaic/docs/migration.md +++ b/packages/mosaic/docs/migration.md @@ -124,4 +124,4 @@ Tests green, then a changeset and a conventional commit. See the `clerk-monorepo skill for the dev loop and the hard rules, including which changeset to write. The commit scope is `mosaic`. If the migration removes or changes legacy code in `packages/ui`, remember non-major `packages/ui` changes load into older SDKs in -the wild, so keep its public surface backwards compatible. +the wild, so keep its public surface backward compatible. diff --git a/packages/mosaic/docs/motion.md b/packages/mosaic/docs/motion.md index c3456049e83..541cffc522b 100644 --- a/packages/mosaic/docs/motion.md +++ b/packages/mosaic/docs/motion.md @@ -44,7 +44,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 @@ -99,7 +99,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. @@ -292,7 +292,7 @@ curves. A hover is not, and neither is a scrim, which dims the page rather than arriving on it. Nothing moves, so there is nothing for an ease to sell. 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 | @@ -408,7 +408,7 @@ duration short enough to fix it is long enough to be worth having. Instant track is also what platform menus have always done. 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/packages/mosaic/docs/stylex.md b/packages/mosaic/docs/stylex.md index cefcc90ffb0..9016ee7cb59 100644 --- a/packages/mosaic/docs/stylex.md +++ b/packages/mosaic/docs/stylex.md @@ -66,7 +66,7 @@ Keys starting with `--` emit verbatim (the public `--cl-*` handles, see `--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. @@ -191,7 +191,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 @@ -216,7 +216,7 @@ device, while touch devices look correct. `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. diff --git a/packages/mosaic/docs/views.md b/packages/mosaic/docs/views.md index 9603b61bf07..8bb93326a94 100644 --- a/packages/mosaic/docs/views.md +++ b/packages/mosaic/docs/views.md @@ -127,14 +127,14 @@ finalFocus={removalFocus.finalFocus} onConfirm={method => removalFocus.remove(method.id)} ``` -A cancelled or failed removal keeps its own trigger, so `finalFocus` returns +A canceled or failed removal keeps its own trigger, so `finalFocus` returns `null` and the dialog's default (the trigger) applies. 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/packages/mosaic/src/blocks/confirmation/__tests__/confirmation.controller.test.ts b/packages/mosaic/src/blocks/confirmation/__tests__/confirmation.controller.test.ts index b8aec6ccf65..c5a24fe9a93 100644 --- a/packages/mosaic/src/blocks/confirmation/__tests__/confirmation.controller.test.ts +++ b/packages/mosaic/src/blocks/confirmation/__tests__/confirmation.controller.test.ts @@ -67,7 +67,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 9fc63d58d74..325e516f98f 100644 --- a/packages/mosaic/src/blocks/confirmation/confirmation.tsx +++ b/packages/mosaic/src/blocks/confirmation/confirmation.tsx @@ -91,7 +91,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; @@ -165,7 +165,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/__tests__/destructive.controller.test.ts b/packages/mosaic/src/blocks/destructive/__tests__/destructive.controller.test.ts index ddae78d1491..0ad1dc2aca2 100644 --- a/packages/mosaic/src/blocks/destructive/__tests__/destructive.controller.test.ts +++ b/packages/mosaic/src/blocks/destructive/__tests__/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/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/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 f169d9cef20..6cbd351ff4d 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/__tests__/alert-dialog.test.tsx b/packages/mosaic/src/components/dialog/__tests__/alert-dialog.test.tsx index 80d6b7ad992..b68e98bfa0c 100644 --- a/packages/mosaic/src/components/dialog/__tests__/alert-dialog.test.tsx +++ b/packages/mosaic/src/components/dialog/__tests__/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/__tests__/dialog.test.tsx b/packages/mosaic/src/components/dialog/__tests__/dialog.test.tsx index 0612e91a4c5..9f977b8bd04 100644 --- a/packages/mosaic/src/components/dialog/__tests__/dialog.test.tsx +++ b/packages/mosaic/src/components/dialog/__tests__/dialog.test.tsx @@ -605,7 +605,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/dialog/dialog.styles.ts b/packages/mosaic/src/components/dialog/dialog.styles.ts index a27f1d662b9..607bdf4333f 100644 --- a/packages/mosaic/src/components/dialog/dialog.styles.ts +++ b/packages/mosaic/src/components/dialog/dialog.styles.ts @@ -41,7 +41,7 @@ const WIDE = '@container cl-dialog (width >= 90rem)'; const SHEET = '@media (width < 40rem)'; 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 @@ -164,7 +164,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: @@ -574,7 +574,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 @@ -646,7 +646,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})`, @@ -660,7 +660,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. // @@ -740,7 +740,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/keyboard-touch.ts b/packages/mosaic/src/components/dialog/keyboard-touch.ts index 97c50f68d8e..babd8742e0e 100644 --- a/packages/mosaic/src/components/dialog/keyboard-touch.ts +++ b/packages/mosaic/src/components/dialog/keyboard-touch.ts @@ -17,7 +17,7 @@ function handlesOwnDrag(element: Element): boolean { * dialog instead. * - Tapping a submit button leaves focus on the field, so the keyboard stays up while the form * submits and through to whatever field comes next. - * - A one-finger drag is cancelled unless something under it can scroll that way or takes the drag + * - A one-finger drag is canceled unless something under it can scroll that way or takes the drag * itself. With the keyboard up, iOS would otherwise pan the visual viewport over the page; with * it down, the drag would only rubber-band the scroll-locked page. */ diff --git a/packages/mosaic/src/components/field/__tests__/field.test.tsx b/packages/mosaic/src/components/field/__tests__/field.test.tsx index b2e00aadf46..98691ba6444 100644 --- a/packages/mosaic/src/components/field/__tests__/field.test.tsx +++ b/packages/mosaic/src/components/field/__tests__/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 de6a6f807e4..aec6067bab9 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 e2d2325c6c6..265c64759a4 100644 --- a/packages/mosaic/src/components/profile/profile.test.tsx +++ b/packages/mosaic/src/components/profile/profile.test.tsx @@ -129,7 +129,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 c15404abb70..47b91c1d384 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(); @@ -1037,7 +1037,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(); @@ -1226,7 +1226,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(); @@ -1250,7 +1250,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(); @@ -1330,7 +1330,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 7a0760ee2eb..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 @@ -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'; @@ -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)); @@ -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/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 b332f98f39d..fda15e63ab3 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-device-actions.test.tsx b/packages/mosaic/src/features/user-profile/__tests__/user-profile-device-actions.test.tsx index b1b990d31b3..dd3d7c05968 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 @@ -54,7 +54,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); diff --git a/packages/mosaic/src/features/user-profile/user-profile-active-devices-section/user-profile-active-devices-section.view.tsx b/packages/mosaic/src/features/user-profile/user-profile-active-devices-section/user-profile-active-devices-section.view.tsx index 7a48f1330ae..a06bfb93e7f 100644 --- a/packages/mosaic/src/features/user-profile/user-profile-active-devices-section/user-profile-active-devices-section.view.tsx +++ b/packages/mosaic/src/features/user-profile/user-profile-active-devices-section/user-profile-active-devices-section.view.tsx @@ -56,7 +56,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-danger-section/__tests__/user-profile-danger-section.integration.test.tsx b/packages/mosaic/src/features/user-profile/user-profile-danger-section/__tests__/user-profile-danger-section.integration.test.tsx index 6e1d228a869..4973d2d172b 100644 --- a/packages/mosaic/src/features/user-profile/user-profile-danger-section/__tests__/user-profile-danger-section.integration.test.tsx +++ b/packages/mosaic/src/features/user-profile/user-profile-danger-section/__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/user-profile-passkeys-section/__tests__/user-profile-passkeys-cleanup.feature.test.tsx b/packages/mosaic/src/features/user-profile/user-profile-passkeys-section/__tests__/user-profile-passkeys-cleanup.feature.test.tsx index a958d22b074..c1b954bed5f 100644 --- a/packages/mosaic/src/features/user-profile/user-profile-passkeys-section/__tests__/user-profile-passkeys-cleanup.feature.test.tsx +++ b/packages/mosaic/src/features/user-profile/user-profile-passkeys-section/__tests__/user-profile-passkeys-cleanup.feature.test.tsx @@ -10,7 +10,7 @@ import { UserProfilePasskeysSection } from '../user-profile-passkeys-section'; 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' })); diff --git a/packages/mosaic/src/features/user-profile/user-profile-password-section/__tests__/user-profile-edit-password.controller.test.ts b/packages/mosaic/src/features/user-profile/user-profile-password-section/__tests__/user-profile-edit-password.controller.test.ts index 059dd8fdbb5..56e8a227e2a 100644 --- a/packages/mosaic/src/features/user-profile/user-profile-password-section/__tests__/user-profile-edit-password.controller.test.ts +++ b/packages/mosaic/src/features/user-profile/user-profile-password-section/__tests__/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/__tests__/use-list-removal-focus.test.ts b/packages/mosaic/src/hooks/__tests__/use-list-removal-focus.test.ts index 7e11549bf1f..cc316589310 100644 --- a/packages/mosaic/src/hooks/__tests__/use-list-removal-focus.test.ts +++ b/packages/mosaic/src/hooks/__tests__/use-list-removal-focus.test.ts @@ -58,7 +58,7 @@ describe('useListRemovalFocus', () => { expect(result.current.finalFocus()).toBe(buttons.get('c')); }); - it('targets nothing when no row was removed, so a cancelled confirmation keeps its default', () => { + it('targets nothing when no row was removed, so a canceled confirmation keeps its default', () => { const { result } = setup(['a', 'b'], vi.fn()); expect(result.current.finalFocus()).toBeNull(); diff --git a/packages/mosaic/src/hooks/use-accessible-name-warning.ts b/packages/mosaic/src/hooks/use-accessible-name-warning.ts index 7c750f94535..cb2316d2723 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 fe58f7de96b..a09feca180b 100644 --- a/packages/mosaic/src/localization/errors.messages.ts +++ b/packages/mosaic/src/localization/errors.messages.ts @@ -42,8 +42,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 d527868354d..081b6d3b538 100644 --- a/packages/mosaic/src/machine/create-actor.ts +++ b/packages/mosaic/src/machine/create-actor.ts @@ -86,7 +86,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 5ae3a960161..195d582a1ec 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/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/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 b26438bf192..21dff97421d 100644 --- a/packages/ui/src/components/ConfigureDirectorySync/ConfigureDirectorySyncContext.tsx +++ b/packages/ui/src/components/ConfigureDirectorySync/ConfigureDirectorySyncContext.tsx @@ -93,7 +93,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 de57883e63e..05707d5ad0a 100644 --- a/packages/ui/src/components/ConfigureDirectorySync/__tests__/ConfigureDirectorySyncWizard.test.tsx +++ b/packages/ui/src/components/ConfigureDirectorySync/__tests__/ConfigureDirectorySyncWizard.test.tsx @@ -140,7 +140,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/ConfigureDirectorySync/__tests__/RoleMappingStep.test.tsx b/packages/ui/src/components/ConfigureDirectorySync/__tests__/RoleMappingStep.test.tsx index 41c00ef34d1..20f1f949328 100644 --- a/packages/ui/src/components/ConfigureDirectorySync/__tests__/RoleMappingStep.test.tsx +++ b/packages/ui/src/components/ConfigureDirectorySync/__tests__/RoleMappingStep.test.tsx @@ -228,7 +228,7 @@ describe('RoleMappingStep', () => { expect(dir.replaceGroupRoleMappings).not.toHaveBeenCalled(); }); - it('leaves role sync off when enabling is cancelled', async () => { + it('leaves role sync off when enabling is canceled', async () => { const { userEvent } = await renderRolesStep(directory({ groupRoleMappingEnabled: false })); await screen.findByTestId('role-mapping-row-dirgrp_leads'); @@ -287,7 +287,7 @@ describe('RoleMappingStep', () => { ); }); - it('leaves role sync on when disabling is cancelled', async () => { + it('leaves role sync on when disabling is canceled', async () => { const { userEvent } = await renderRolesStep(directory({ groupRoleMappingEnabled: true })); await screen.findByTestId('role-mapping-row-dirgrp_leads'); 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/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/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/internal/appearance.ts b/packages/ui/src/internal/appearance.ts index 23199b6bbaa..26b0aa7510c 100644 --- a/packages/ui/src/internal/appearance.ts +++ b/packages/ui/src/internal/appearance.ts @@ -1082,7 +1082,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 */ @@ -1092,7 +1092,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 */ @@ -1110,14 +1110,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/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, 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 }; diff --git a/packages/ui/src/utils/__tests__/cssLayerName.test.ts b/packages/ui/src/utils/__tests__/cssLayerName.test.ts index cdf1b3418d8..a3ae1017e69 100644 --- a/packages/ui/src/utils/__tests__/cssLayerName.test.ts +++ b/packages/ui/src/utils/__tests__/cssLayerName.test.ts @@ -25,7 +25,7 @@ const rangeEdges = [...new Set(NON_ASCII_IDENT_RANGES.flatMap(([lo, hi]) => [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 d7d81caee05..d8c0cc8ff43 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); }