Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/expo-verify-skill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
19 changes: 12 additions & 7 deletions .claude/skills/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@ Edits to a `SKILL.md` take effect immediately, including in already-running sess

## Scope

Skills are Claude Code specific. Cursor does not read this directory; it uses `.cursor/rules/` and
`AGENTS.md`. When a repo rule changes, update `AGENTS.md` first, then mirror the change here and in
`.cursor/rules/` where relevant.
Skills here are Claude Code specific, except `verify-clerk-expo`, the agent-neutral skill that drives
`@clerk/expo` on a simulator or emulator. Its `SKILL.md` and references live here, and the tests and
the CLI it drives are the package `integration/expo-native/`. `.cursor/skills/verify-clerk-expo` is a
symlink to this directory so Cursor reads the same skill. Edit it here, not through the
symlink. For the other skills, Cursor uses `.cursor/rules/` and `AGENTS.md`. When a repo rule
changes, update `AGENTS.md` first, then mirror the change here and in `.cursor/rules/` where
relevant.

## Maintaining a skill

Expand All @@ -41,7 +45,8 @@ Skills are Claude Code specific. Cursor does not read this directory; it uses `.

## Skills in this repo

| Skill | Use it for |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `clerk-monorepo` | Day-to-day work in the monorepo: setup, build/test loops, the package map, changesets, commits, PRs, breaking-change checks. |
| `mosaic` | Mosaic flow UI: authoring machines, controllers, and views, and migrating a legacy component into the split (with parity verification). |
| Skill | Use it for |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `clerk-monorepo` | Day-to-day work in the monorepo: setup, build/test loops, the package map, changesets, commits, PRs, breaking-change checks. |
| `mosaic` | Mosaic flow UI: authoring machines, controllers, and views, and migrating a legacy component into the split (with parity verification). |
| `verify-clerk-expo` | Proving a `@clerk/expo` change on an iOS simulator or Android emulator, with video and screenshots as evidence. |
223 changes: 223 additions & 0 deletions .claude/skills/verify-clerk-expo/SKILL.md

Large diffs are not rendered by default.

54 changes: 54 additions & 0 deletions .claude/skills/verify-clerk-expo/features/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# @clerk/expo verification map

This directory is the maintained source for verifying the user-facing behavior of `@clerk/expo` through the `expo-native` test app in `integration/templates/expo-native`. On a local device the test app runs as a Debug dev client that loads its JS, and `packages/expo` with it, from a local Metro server. Read this index before driving the app, then use the matching feature file as the recipe. Every recipe runs through `integration/expo-native/bin/control-clerk-expo` and the golden specs under `specs/golden/<feature>/`.

## Test users and sign-in

`.claude/skills/verify-clerk-expo/SKILL.md` has the rules for signing in. These are the test identities and the identifiers a spec needs.

- **Emails.** Any address that contains `+clerk_test@` is a test address. Clerk sends no mail and accepts the code below. `host.newEmail()` and `host.seedUser()` mint an address such as `1412107c1ek3x9+clerk_test@example.com`, new per run.
- **One-time code.** `424242` verifies every email code for a test address. Specs use the constant `CLERK_TEST_CODE`.
- **Passwords.** The standard settings require a password at sign-up. Use a throwaway per run, such as `Verify-<runId>-Pw1!`. For a password sign-in, seed the user with `host.seedUser({ password: true })` and fill the field with `host.fill(field, user.password!)`.

| Step | Native AuthView (iOS identifier) | Custom flow (test app testID) |
| ------------------------------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------- |
| Email field | `clerk.auth.start.identifier` | `verify.customSignIn.emailAddress`, `verify.customSignUp.emailAddress` |
| Switch between email and phone | `clerk.auth.start.identifierSwitcher` | none |
| Phone field | `clerk.auth.start.phoneNumber` | none |
| Continue or send code | `clerk.auth.start.continue` | `verify.customSignIn.sendCode`, `verify.customSignUp.sendCode` |
| Try another method on the email link screen | the `Use another method` text | none |
| Pick a method from the list | `clerk.auth.signIn.alternativeMethod.<strategy>` | none |
| Sign-in code field | `clerk.auth.signIn.code` | `verify.customSignIn.code`, then `verify.customSignIn.verifyCode` |
| Sign-in password | `clerk.auth.signIn.password`, then `clerk.auth.signIn.continue` | none |
| Sign-up password | `clerk.auth.signUp.password` | `verify.customSignUp.password` |
| Sign-up code field | `clerk.auth.signUp.code` | `verify.customSignUp.code`, then `verify.customSignUp.verifyCode` |
| Close AuthView | `clerk.dismissButton` | none |

The iOS identifiers come from `Sources/ClerkKitUI/Components/Auth/ClerkAccessibilityIdentifiers.swift` in the clerk-ios release that `packages/expo-native-components/ios/ClerkExpo.podspec` pins. The clerk-android release that `packages/expo-native-components/android/build.gradle` pins has no test tags, so Android specs find native views by their text. The custom-flow testIDs live in `integration/templates/expo-native/screens/`.

## Driving conventions

- Prefer SDK identifiers on iOS and test app testIDs everywhere. `specs/native.ts` holds the per-platform locators for native views that have no Android tag yet.
- Look first in the native view on the spec's path: the AuthView code screen, or the profile and its `Manage account` screen. Use the `Token cache` screen for what the token cache held when the app started.

## Proof and skip reporting

- A proof is a passing `run` whose run directory holds `video.mp4`, `screenshots/`, `app.log`, and `e2e/report.json`. The video and the screenshots are the proof on both platforms.
- Name the run id, the platform, and the specs in the PR. Attach the run with `attach <run-id> --pr <n>`.
- A spec limited to one platform reports as skipped on the other. Say which platform each proof ran on.

## Feature entry contract

Each feature file starts with an H1 title and one paragraph describing the user-visible behavior, then exactly four H2 sections in this order: `Sub-features`, `How to get to it (user POV)`, `Driving it with verify`, and `Gotchas`. `Driving it with verify` starts with `Preconditions:` and names the golden specs that prove each sub-feature. A feature file lists only entry points that a spec drives.

## Features

- [Native AuthView](./native-auth-view.md) covers opening, closing, and signing in through the native AuthView, and its React Native logo.
- [User button and profile](./user-button-and-profile.md) covers the native UserButton, the profile it opens, the home's sign-out button, and an inline UserProfileView with `onHostBack` and a custom page.
- [Custom sign-in flow](./custom-flow-sign-in.md) covers an email code sign-in built on `useSignIn`.
- [Custom sign-up flow](./custom-flow-sign-up.md) covers an email and password sign-up built on `useSignUp`.
- [Token cache persistence](./token-cache-persistence.md) covers restoring the same session after a relaunch.
- [Native and JS session sync](./native-js-sync.md) covers a native sign-out and a native password sign-in reaching `useAuth`, `useUser`, and `useSession`, and that sign-in surviving a relaunch.
- [Native modules](./native-modules.md) covers `useSignInWithGoogle` opening the native Google sign-in, a cancel of it on iOS, and `useBiometricCredentials` reporting what its native module found on the device.

Not mapped yet: SSO through `useSSO` (no real OAuth on simulators), a Google or Apple native sign-in with a real account, passkeys, enrolling or using a biometric (no associated domains or Secure Enclave on the simulator), and session tasks in the native views.
29 changes: 29 additions & 0 deletions .claude/skills/verify-clerk-expo/features/custom-flow-sign-in.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Custom sign-in flow

An app that builds its own sign-in screen on `useSignIn` sends an email code to an existing user and signs them in with it.

## Sub-features

- `complete` verifies the code, finalizes the sign-in, and returns to the home, which shows the user and the session.

## How to get to it (user POV)

- Tap `Custom sign-in` on the signed-out home. The screen has an email field, `Send code`, then a code field and `Verify code`.

## Driving it with verify

Preconditions:

- The standard settings turn the `email_code` strategy on. `up` fails with `INSTANCE_MISCONFIGURED` when the application does not show it.
- The spec seeds its own `+clerk_test` user.

- **Request the code.** Run `integration/expo-native/bin/control-clerk-expo run custom-flow-sign-in/complete`. The spec fills `verify.customSignIn.emailAddress`, taps `verify.customSignIn.sendCode`, and expects `verify.customSignIn.code` with no `verify.customSignIn.error`. The form shows the code field only when `signIn.emailCode.sendCode` returned no error. Screenshot `custom-code`.
- **Enter the code.** The spec then types `CLERK_TEST_CODE` and taps `verify.customSignIn.verifyCode`, and `host.expectSignedInAs(user)` waits for the home to show the user's email, the seeded user ID, and a session ID. Screenshot `custom-signed-in`.
- **Proof.** The spec passes on the platform you changed.

## Gotchas

- These are React Native views with `testID`s, so plain locator actions work. `host.tap` and `host.fill` also work and keep specs uniform. On iOS an empty one reads as its placeholder, and `host.fill` replaces its contents once when the field does not hold the text.
- A sign-in error shows in `verify.customSignIn.error`, and the form stays on screen. Read the error text from the failure page when a spec stops on the email screen.
- The test app shows the form only while nobody has an active session. Once the sign-in is finalized it shows the home, so the form on screen also means that nobody is signed in yet.
- The screen keeps no state across launches. Every launch starts on the home, and the form opens on the email field.
27 changes: 27 additions & 0 deletions .claude/skills/verify-clerk-expo/features/custom-flow-sign-up.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Custom sign-up flow

An app that builds its own sign-up screen on `useSignUp` creates a user with an email address and password, then verifies the email code.

## Sub-features

- `complete` verifies the code, finalizes the sign-up, and returns to the home, which shows the new user and the session.

## How to get to it (user POV)

- Tap `Custom sign-up` on the signed-out home. The screen has email and password fields, `Send code`, then a code field and `Verify code`.

## Driving it with verify

Preconditions:

- The standard settings require a password at sign-up. The spec uses `Verify-<runId>-Pw1!`.
- The spec gets a new `+clerk_test` address from `host.newEmail`. The address starts with the run's prefix, and that is how the run finds the user the form creates. `integration/expo-native/bin/control-clerk-expo down` deletes the application and with it the user the form creates.

- **Request the code.** Run `integration/expo-native/bin/control-clerk-expo run custom-flow-sign-up/complete`. The spec fills `verify.customSignUp.emailAddress` and `verify.customSignUp.password`, taps `verify.customSignUp.sendCode`, and expects `verify.customSignUp.code` with no `verify.customSignUp.error`. The form shows the code field only when `signUp.create` and `sendEmailCode` returned no error. Screenshot `custom-signup-code`.
- **Enter the code.** The spec then types `CLERK_TEST_CODE` and taps `verify.customSignUp.verifyCode`, and `host.expectSignedInAs(email)` waits for the home to show the new email, a user ID, and a session ID. Screenshot `custom-signed-up`.
- **Proof.** The spec shows the home with the new email, a user ID, and a session ID.

## Gotchas

- Type only an address that `host.newEmail` returned, so the run finds the identity it created.
- The test app's password field has the one-time-code content type (`textContentType='oneTimeCode'` in `screens/CustomSignUp.tsx`). Without it, iOS covers the keyboard with a `Use Strong Password?` sheet as soon as the field has focus. With it, a spec that only focuses the field sees the keyboard and no sheet. The specs that type the password pass with it.
Loading
Loading