Skip to content
Closed

t3st #9351

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
49d61ff
feat(ui): scale Mosaic Item rows from a single size prop (#9297)
alexcarpenter Jul 31, 2026
aaea141
feat(ui): remove experimental gate from self-serve OIDC flow (#9288)
NicolasLopes7 Jul 31, 2026
ebab8bb
chore(expo): bump clerk-android to 1.0.39 (#9300)
clerk-cookie Jul 31, 2026
6a5eac5
chore(expo): bump clerk-ios to 1.3.6 (#9301)
clerk-cookie Jul 31, 2026
2100e4b
feat(ui): add Mosaic Menu component (#9252)
alexcarpenter Jul 31, 2026
b3f9243
feat(expo): let native components keep their own chrome when embedded…
mikepitre Jul 31, 2026
4e0a1c2
feat(ui): split the Mosaic popover from its surface- #9295 (#9296)
maxyinger Jul 31, 2026
38f347c
ci(repo): Version packages (#9289)
clerk-cookie Jul 31, 2026
fa761dc
fix(fastify,express): Respond 400 instead of 500 to requests that can…
wobsoriano Jul 31, 2026
6b24470
feat(ui): grow the Mosaic Button touch target without growing the but…
maxyinger Jul 31, 2026
bf1b62a
fix(*): recover from duplicate session cookies (#9286)
thiskevinwang Jul 31, 2026
a601cd7
fix(backend): Update params and documention on createUser (#9305)
dmoerner Jul 31, 2026
66d5431
feat(ui): fade Mosaic Button icons until hover (#9307)
alexcarpenter Jul 31, 2026
355839d
refactor(ui): migrate Mosaic Card to StyleX (#9292)
austincalvelage Jul 31, 2026
7f0cac8
fix(clerk-js): complete the Safari ITP touch hop in redirect (#9308)
dmoerner Jul 31, 2026
ec24507
refactor(ui): type Mosaic render callbacks from MosaicComponentProps …
alexcarpenter Jul 31, 2026
fdadaa2
test(expo): Restore green expo native Maestro flows and enforce them …
wobsoriano Jul 31, 2026
67c81e2
feat(ui): Add a Mosaic scroll area with scroll-driven edge fades (#9312)
maxyinger Aug 3, 2026
8b420c2
fix(headless): stop a menu inside a popover acting as a submenu (#9315)
alexcarpenter Aug 3, 2026
0ba9dbe
feat(ui): make Mosaic button hover land instantly and fade out (#9298)
maxyinger Aug 3, 2026
bbe51ff
fix(react): make ClerkProvider publishableKey optional (#9314)
SarahSoutoul Aug 4, 2026
5c81479
feat(ui): Support signUpIfMissing with Clerk <SignIn> component (#7928)
dmoerner Aug 4, 2026
83a8fc5
fix(ui): Fix email link race with sign up if missing (#9328)
dmoerner Aug 4, 2026
438f2e5
ci(repo): Version packages (#9306)
clerk-cookie Aug 4, 2026
a66cbbf
docs(*): rename "Client Trust" to "Device Trust" (#9266)
mwickett Aug 4, 2026
d639048
feat(js): add InviteMembersButton and Clerk.openInviteMembers (#9124)
alexcarpenter Aug 5, 2026
4717aab
fix(ui): Give each Mosaic component a minimal CSS reset (#9332)
alexcarpenter Aug 5, 2026
1ef84c3
fix(backend,shared): fix generated TypeDoc references (#9340)
SarahSoutoul Aug 5, 2026
f38cf02
feat(backend): support removing user passwords (#9326)
joshrowley Aug 5, 2026
58d8ff5
fix(react): Detect nested ClerkProvider via context instead of a glob…
wobsoriano Aug 6, 2026
a53c672
ci(repo): Version packages (#9334)
clerk-cookie Aug 6, 2026
1799158
refactor(ui): Mosaic input component (#9309)
austincalvelage Aug 6, 2026
2c464a3
fix(headless): keep pointer interactions from painting a focus ring (…
alexcarpenter Aug 6, 2026
63d25ba
docs(backend): Use root-relative link for passwordHasher contact supp…
manovotny Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 5 additions & 0 deletions .changeset/backend-relative-support-link.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/backend': patch
---

Use a root-relative link (`/contact/support`) for the `passwordHasher` "contact support" reference so the generated API reference renders it as an internal same-tab link instead of an external one.
15 changes: 0 additions & 15 deletions .changeset/bright-taxis-sing.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/decodejwt-malformed-token.md

This file was deleted.

2 changes: 0 additions & 2 deletions .changeset/mosaic-button-variants.md

This file was deleted.

2 changes: 0 additions & 2 deletions .changeset/mosaic-item.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/tame-bats-tell.md

This file was deleted.

6 changes: 0 additions & 6 deletions .changeset/tame-donuts-shake.md

This file was deleted.

1 change: 1 addition & 0 deletions .claude/skills/mosaic/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ this skill is the _how-to_.
| -------------------------------------------------------------------- | ------------------------------------------------------ |
| Building on / authoring a headless primitive (`@clerk/headless`) | `references/headless.md` |
| Styling a component with StyleX (tokens, `stylex.create`, CSS build) | `references/stylex.md` |
| Building an enter/exit transition, or any motion that reads as wrong | `references/motion.md` |
| Styling a component the legacy way (slot recipes, `useRecipe`) | `references/styling.md` |
| Authoring or debugging a state machine, or wiring one to React | `references/machines.md` → in-tree `machine/README.md` |
| Writing the controller (Clerk adapter, permissions, revalidate) | `references/controllers.md` |
Expand Down
441 changes: 441 additions & 0 deletions .claude/skills/mosaic/references/motion.md

Large diffs are not rendered by default.

119 changes: 118 additions & 1 deletion .claude/skills/mosaic/references/stylex.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,28 @@ device, while touch devices look correct.
then grep `dist-mosaic/styles.css` for the two selectors and compare their
specificity.

- **A button that opens something takes the pressed fill while open**, so a
disclosure trigger stays visibly engaged for as long as its surface is. Disclosure
primitives already set `data-open` on the trigger (`popover-trigger.tsx` and
friends), so this is styling-only — no headless change. It needs the _same_
exclusion as `:active`, or hovering an open trigger lifts it back to the lighter
hover step:

```ts
backgroundColor: {
default: 'transparent',
':enabled:active': neutralStep1,
':enabled[data-open]': neutralStep1,
'@media (hover: hover)': {
default: null,
':enabled:hover:not(:active):not([data-open])': neutralStep0,
},
},
```

Worked example: `button.styles.ts`, applied across every filled/outline/ghost cell
(`link` opts out — it reads as text, not a control).

Worked example: `packages/ui/src/mosaic/components/button/button.styles.ts`.

- **DO** use `:focus-visible` for focus rings (never bare `:focus`). For a
Expand Down Expand Up @@ -341,6 +363,12 @@ Worked example: `packages/ui/src/mosaic/components/button/button.styles.ts`.
drag, and an overshoot extrapolates past the target color for nothing. A
transform at `--cl-duration-fast` still wants the curve.

- **DON'T** reuse `--cl-ease-default` for something **leaving**. It is an arrival
curve; run backwards it stalls for most of its duration and its overshoot
becomes a wobble past the target. Departures take `easingVars['--cl-ease-exit']`
at a shorter duration. See `motion.md` — enter/exit asymmetry has its own
reference, with the measurements behind these rules.

- **DO** gate transitions/animations of **motion-bearing** properties on reduced
motion — `transform`, `translate`, `scale`, `rotate`, positional insets — in the
same object. `prefers-reduced-motion` is a vestibular-safety signal, so color
Expand Down Expand Up @@ -468,6 +496,61 @@ value, sub-pattern A collapses it to a single `--var` atom; reach for a raw inli
> values, and even then the first move is usually to write a single `--cl`/`--_cl`
> var rather than a raw inline style.

**Every condition is a value key, never a top-level object.** A pseudo/at-rule
goes _inside_ the property it modifies (`transitionProperty: { default: …, '@media …': … }`),
not as a bare key on the style object. A top-level `'@media …': { … }` block is
legacy syntax and the `@stylexjs/no-legacy-contextual-styles` +
`@stylexjs/valid-styles` rules reject it (only `::before`/`::after` may sit at the
top level). Reduced-motion is the common case:

```ts
transitionProperty: { default: 'opacity, transform', '@media (prefers-reduced-motion: reduce)': 'none' },
```

### Reacting to `data-*` state (the headless-transition case)

Headless primitives drive animation off `data-*` attributes — e.g. the popover
popup carries its own `data-starting-style` (entering frame) and
`data-ending-style` (exiting). You can style off these in StyleX; it depends on
_whose_ attribute you're reading:

- **The element's own attribute → wrap in `:where(...)`.** Conditional keys must
start with `:` or `@`, so a bare `[data-*]` is rejected — but `:where([data-*])`
is a valid pseudo-class string that matches the same element (zero specificity;
StyleX self-doubles the atom class so the conditional still wins):

```ts
popup: {
opacity: { default: 1, ':where([data-starting-style], [data-ending-style])': 0 },
transform: { default: 'scale(1)', ':where([data-starting-style], [data-ending-style])': 'scale(0.94)' },
// Reduced motion drops `transform` and keeps the fade — the gate belongs on the
// moving property, not the whole transition.
transitionProperty: { default: 'opacity, transform', '@media (prefers-reduced-motion: reduce)': 'opacity' },
// Positional against `transitionProperty`, and branched by direction: the exit is
// shorter and takes the departure curve. See `motion.md`.
transitionDuration: {
default: `${durationVars['--cl-duration-fast']}, ${durationVars['--cl-duration-base']}`,
':where([data-ending-style])': durationVars['--cl-duration-fast'],
},
transitionTimingFunction: {
default: `linear, ${easingVars['--cl-ease-default']}`,
':where([data-ending-style])': `linear, ${easingVars['--cl-ease-exit']}`,
},
},
```

- **Another element's attribute → `stylex.when.*`.** For relational state use
`stylex.when.ancestor(sel)` / `.descendant(sel)` / `.siblingBefore(sel)` /
`.siblingAfter(sel)` / `.anySibling(sel)`, each taking a `:${string}` or
`[${string}]` selector and returning a valid conditional key
(`:where-ancestor(...)` etc.). Use this when a parent/sibling owns the state
(e.g. a `[data-open]` container theming its children); use `:where([data-*])`
when the element owns it.

So: `:where(...)` for self-state, `stylex.when.*` for relational state. Both
compile to real attribute selectors in `styles.css`, so animation stays
CSS-native — no JS state plumbing through the component.

## Public contract & composition (`props.ts`)

The element carries three things, and nothing else is a contract:
Expand Down Expand Up @@ -501,6 +584,39 @@ className left-to-right and merges `style` with the consumer object spread last:
- **DON'T** call `stylex.props` twice on one element or spread `{...props}` after
the merge result — fuse everything through the one `mergeStyleProps` call.

### Type every part with `MosaicComponentProps`

`MosaicComponentProps<Tag>` is the native props for `Tag` minus the non-standard HTML
`color` attribute, plus `render`. It drops `color` from the props **and** from the
`render` callback's argument, so a callback's props spread straight into a Mosaic
component whose own `color` is a variant union (`Button`, `Heading`, `Text`).

```tsx
export interface PopoverPopupProps extends MosaicComponentProps<'div'> { … }
```

- **DON'T** type a Mosaic part with the headless `ComponentProps<Tag>` (or
`React.ComponentPropsWithoutRef<typeof Primitive.X>`). Those keep `color: string`, and
every consumer then has to strip it: `props: Omit<React.HTMLAttributes<HTMLElement>, 'color'>`.
- **DON'T** re-export a headless part straight onto the Mosaic namespace object
(`Popover.Trigger = Primitive.Trigger`) — that leaks the wide type. Bridge it:

```tsx
const Trigger = React.forwardRef<HTMLButtonElement, MosaicComponentProps<'button'>>(
function PopoverTrigger(props, ref) {
return (
<Primitive.Trigger
ref={ref}
{...props}
/>
);
},
);
```

- If a consumer needs to annotate a `render` callback, the API is wrong — fix the
part's props type instead. Inline callbacks infer with no annotation.

## Build & CSS delivery (two contexts, same babel)

- **Published** (`build:mosaic` → `@stylexjs/rollup-plugin`): compiles the
Expand All @@ -526,7 +642,8 @@ token colors aren't down-leveled into an invalid polyfill.
`::before`/`::after`/`::backdrop`, `@starting-style` (enter animations),
`stylex.keyframes(...)`, `anchor-size(width|height)` (popover/menu matching its
trigger), CSS counters, `@media (hover: hover)` / `(prefers-reduced-motion)` /
`(pointer: coarse)`.
`(pointer: coarse)`, `data-*` state via `:where([data-*])` (self) or
`stylex.when.*` (relational) — see "Reacting to `data-*` state" above.
- Prefer CSS-native solutions over JS workarounds for anything StyleX supports.
- Avoid manual `@layer` / `@property` inside `create` (StyleX owns layering;
`@property` compiles but emits invalid output).
Expand Down
42 changes: 16 additions & 26 deletions .github/workflows/expo-native-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ env:
SDK_PACK_DIR: /tmp/clerk-expo-pack
E2E_INSTANCE_NAME: clerkstage-with-native-components
BAPI_URL: https://api.clerkstage.dev
MAESTRO_RUNNER_VERSION: '1.1.21'
MAESTRO_VERSION: '2.8.0'

jobs:
native-build:
Expand Down Expand Up @@ -214,33 +214,32 @@ jobs:
path: ${{ steps.native-build-key.outputs.artifact }}
key: ${{ steps.native-build-cache.outputs.cache-primary-key }}

- name: Cache maestro-runner
- name: Cache maestro CLI
if: steps.keys.outputs.pk != ''
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.maestro-runner
key: maestro-runner-${{ runner.os }}-${{ runner.arch }}-${{ env.MAESTRO_RUNNER_VERSION }}
path: ~/.maestro
key: maestro-${{ runner.os }}-${{ env.MAESTRO_VERSION }}

- name: Install maestro-runner
- name: Install maestro CLI
if: steps.keys.outputs.pk != ''
run: |
set -o pipefail
if [ -x "$HOME/.maestro-runner/bin/maestro-runner" ]; then
echo "Using cached maestro-runner"
if [ -x "$HOME/.maestro/bin/maestro" ]; then
echo "Using cached Maestro"
else
installed=0
for i in 1 2 3; do
if curl -fsSL --retry 3 --retry-delay 5 "https://open.devicelab.dev/install/maestro-runner" |
bash -s -- --version "$MAESTRO_RUNNER_VERSION"; then
if [ -x "$HOME/.maestro-runner/bin/maestro-runner" ]; then installed=1; break; fi
if curl -fLs --retry 3 --retry-delay 5 "https://get.maestro.mobile.dev" | MAESTRO_VERSION="$MAESTRO_VERSION" bash; then
if [ -x "$HOME/.maestro/bin/maestro" ]; then installed=1; break; fi
fi
echo "maestro-runner install attempt $i failed (or binary missing); retrying"
echo "Maestro install attempt $i failed (or binary missing); retrying"
sleep 5
done
[ "$installed" = 1 ] || { echo "::error::maestro-runner install failed after 3 attempts"; exit 1; }
[ "$installed" = 1 ] || { echo "::error::Maestro install failed after 3 attempts"; exit 1; }
fi
echo "$HOME/.maestro-runner/bin" >> "$GITHUB_PATH"
"$HOME/.maestro-runner/bin/maestro-runner" --version
echo "$HOME/.maestro/bin" >> "$GITHUB_PATH"
"$HOME/.maestro/bin/maestro" --version

- name: Boot iOS simulator
id: sim
Expand Down Expand Up @@ -281,16 +280,10 @@ jobs:
- name: Run iOS e2e (Maestro)
id: run_e2e_ios
if: matrix.platform == 'ios' && steps.user.outputs.user_id != ''
# Burn-in: e2e cannot fail the check yet. Flip to hard-fail in a
# follow-up once the suite has proven quiet.
continue-on-error: true
working-directory: ${{ env.FIXTURE_DIR }}
env:
CLERK_TEST_EMAIL: ${{ steps.user.outputs.email }}
CLERK_TEST_PASSWORD: ${{ steps.user.outputs.password }}
MAESTRO_DEVICE: ${{ steps.sim.outputs.udid }}
MAESTRO_DRIVER: wda
MAESTRO_PLATFORM: ios
SIM_UDID: ${{ steps.sim.outputs.udid }}
run: |
echo "Using simulator $SIM_UDID"
Expand Down Expand Up @@ -349,13 +342,10 @@ jobs:
- name: Run Android e2e (Maestro)
id: run_e2e_android
if: matrix.platform == 'android' && steps.user.outputs.user_id != ''
continue-on-error: true
uses: reactivecircus/android-emulator-runner@e89f39f1abbbd05b1113a29cf4db69e7540cae5a # v2
env:
CLERK_TEST_EMAIL: ${{ steps.user.outputs.email }}
CLERK_TEST_PASSWORD: ${{ steps.user.outputs.password }}
MAESTRO_DRIVER: devicelab
MAESTRO_PLATFORM: android
with:
api-level: 34
target: google_apis
Expand Down Expand Up @@ -383,7 +373,7 @@ jobs:

# Test reports record flow env (and typed input) in plaintext;
# add-mask only covers step logs, not artifact contents.
- name: Scrub test credentials from maestro-runner reports
- name: Scrub test credentials from Maestro debug output
if: always() && (steps.run_e2e_ios.outcome == 'failure' || steps.run_e2e_android.outcome == 'failure')
env:
CLERK_TEST_PASSWORD: ${{ steps.user.outputs.password }}
Expand All @@ -395,7 +385,7 @@ jobs:
\( -name '*.html' -o -name '*.json' -o -name '*.log' -o -name '*.txt' -o -name '*.xml' -o -name '*.yaml' \) \
-exec perl -pi -e 's/\Q$ENV{CLERK_TEST_PASSWORD}\E/[REDACTED]/g' {} +

- name: Upload maestro-runner artifacts on e2e failure
- name: Upload Maestro artifacts on e2e failure
if: always() && (steps.run_e2e_ios.outcome == 'failure' || steps.run_e2e_android.outcome == 'failure')
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
Expand All @@ -413,5 +403,5 @@ jobs:
[ "$outcome" = "skipped" ] && outcome="$ANDROID_OUTCOME"
echo "## Maestro e2e (${{ matrix.platform }}): $outcome" >> "$GITHUB_STEP_SUMMARY"
if [ "$outcome" = "failure" ]; then
echo "::warning::Maestro e2e failed (burn-in mode: not failing the job). See the maestro-${{ matrix.platform }} artifact."
echo "::error::Maestro e2e failed. See the maestro-${{ matrix.platform }} artifact."
fi
10 changes: 10 additions & 0 deletions .typedoc/custom-plugin.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,19 @@ const LINK_REPLACEMENTS = [
['enterprise-account', '/docs/reference/backend/types/backend-enterprise-account'],
['enterprise-account-connection', '/docs/reference/backend/types/backend-enterprise-account-connection'],
['enterprise-connection', '/docs/reference/backend/types/backend-enterprise-connection'],
[
'enterprise-connection-custom-attribute',
'/docs/reference/backend/types/backend-enterprise-connection-custom-attribute',
],
['enterprise-connection-oauth-config', '/docs/reference/backend/types/backend-enterprise-connection-oauth-config'],
[
'enterprise-connection-saml-connection',
'/docs/reference/backend/types/backend-enterprise-connection-saml-connection',
],
[
'enterprise-connection-saml-connection-login-hint',
'/docs/reference/backend/types/backend-enterprise-connection-saml-connection-login-hint',
],
['external-account', '/docs/reference/backend/types/backend-external-account'],
['phone-number', '/docs/reference/backend/types/backend-phone-number'],
['protect-check-resource', '/docs/reference/types/protect-check-resource'],
Expand All @@ -114,9 +122,11 @@ const LINK_REPLACEMENTS = [
['billing-per-unit-total-tier', '/docs/reference/types/billing-per-unit-total-tier'],
['billing-subscription-item-resource', '/docs/reference/types/billing-subscription-item-resource'],
['billing-subscription-item-seats', '/docs/reference/types/billing-subscription-item-seats'],
['billing-subscription-item-status', '/docs/reference/backend/types/billing-subscription-item-status'],
['feature-resource', '/docs/reference/types/feature-resource'],
['billing-statement-group', '/docs/reference/types/billing-statement-group'],
['billing-statement-resource', '/docs/reference/types/billing-statement-resource'],
['billing-totals', '/docs/reference/types/billing-totals'],
['billing-subscription-resource', '/docs/reference/types/billing-subscription-resource'],
['clerk-api-response-error', '/docs/reference/types/clerk-api-response-error'],
['clerk-api-error', '/docs/reference/types/clerk-api-error'],
Expand Down
Loading
Loading