Skip to content
Merged
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
15 changes: 15 additions & 0 deletions .agents/skills/kitcn/references/features/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -406,11 +406,26 @@ All from `kitcn/react`:
authClient={authClient}
convexQueryClient={convexQueryClient} // when using TanStack Query
initialToken={token} // from SSR (caller.getToken())
optimisticAuth // opt in: open gates while Convex confirms a held JWT
onTokenIdentityChange={() => window.location.reload()}
onMutationUnauthorized={() => router.push('/login')}
onQueryUnauthorized={({ queryName }) => console.log(`Unauth: ${queryName}`)}
>
```

`optimisticAuth` only opens auth-bound query gates for a held, unexpired JWT;
expired, opaque, and refused tokens stay closed. Enable
`onTokenIdentityChange` to refuse a JWT whose `sub` or `sessionId` differs from
the document identity before Convex or HTTP sees it. The client closes before
the callback runs, so reload the document there.

For multiple provider mounts, pass `tokenIdentityBaseline` as
`sub|sessionId`, or a getter returning the document's current identity. The
getter is checked for every admission, cached tokens included.
`onTokenIdentityAdmitted(token)` observes admitted JWTs so the app can update
that shared baseline. All three identity options require
`onTokenIdentityChange`.

For `@convex-dev/auth` (React Native):
```tsx
import { ConvexProviderWithAuth } from 'kitcn/react';
Expand Down
16 changes: 16 additions & 0 deletions .agents/skills/kitcn/references/features/react.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,22 @@ const mutation = useMutation(crpc.user.update.mutationOptions({

Signature: `crpc.path.mutationOptions(options?)` β€” standard TanStack mutation options except `mutationFn`.

Pass `optimisticUpdate(localStore, args)` to use Convex's optimistic local
query store. This option is removed before the TanStack options are returned;
Convex replays it as query data changes and owns rollback when the mutation
completes.

```ts
const mutation = useMutation(crpc.todos.rename.mutationOptions({
optimisticUpdate: (store, args) => {
const todos = store.getQuery(api.todos.list, {});
if (todos) store.setQuery(api.todos.list, {}, todos.map((todo) =>
todo._id === args.id ? { ...todo, title: args.title } : todo
));
},
}));
```

### Mutation Keys

```ts
Expand Down
10 changes: 10 additions & 0 deletions .changeset/optimistic-auth-gate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"kitcn": patch
---

- Add opt-in optimistic auth and document identity admission to
`ConvexAuthProvider`, with the same guarded token source for Convex and cRPC
HTTP requests.
- Add Convex-native `optimisticUpdate` support to cRPC `mutationOptions`.
- Reuse the Convex client's logger for server HTTP clients so construction is
deterministic during prerendering.
398 changes: 398 additions & 0 deletions docs/plans/473-autoclosure.md

Large diffs are not rendered by default.

550 changes: 550 additions & 0 deletions docs/plans/473-optimistic-auth-gate.md

Large diffs are not rendered by default.

5 changes: 0 additions & 5 deletions fixtures/expo-auth/.claude/settings.json

This file was deleted.

42 changes: 40 additions & 2 deletions fixtures/expo-auth/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,41 @@
# Expo HAS CHANGED
This is an Expo/React Native mobile application. Prioritize mobile-first patterns, performance, and cross-platform compatibility.

Read the exact versioned docs at https://docs.expo.dev/versions/v55.0.0/ before writing any code.
## Expo has changed β€” do not trust your training data

Expo ships breaking changes every SDK release. APIs you remember are likely renamed, moved, or removed. Before writing any code that touches an Expo, EAS, or React Native API:

1. Read the major version of the `expo` package in `package.json`.
2. Fetch the matching versioned docs: `https://docs.expo.dev/versions/v<major>.0.0/`
3. For anything else, fetch https://docs.expo.dev/llms.txt β€” an index of all Expo docs with corrections to common LLM misconceptions. Follow its links to the specific page you need; never answer from memory.

## Commands

Use `bunx` instead of `npx` if the project uses bun (`bun.lock` present).

```bash
npx expo install <package> # ALWAYS use instead of npm/yarn/pnpm/bun add β€” resolves SDK-compatible versions
npx expo start # start the dev server
npx expo lint # lint
npx tsc --noEmit # typecheck
npx expo-doctor # diagnose dependency and config issues
npx expo install --fix # fix incompatible package versions
```

Run lint and typecheck before declaring any task done.

## Navigation & Routing

- Use **Expo Router** for all navigation. Routes live in `src/app/` β€” every file there is a screen, `_layout.tsx` files define navigators. Keep non-route code (components, hooks, utils) outside `src/app/`.
- Import `Link`, `router`, and `useLocalSearchParams` from `expo-router`.
- Docs: https://docs.expo.dev/router/introduction.md

## Building with EAS

Use EAS to build, sign, and submit the app in the cloud (`eas build`, `eas submit`) and to ship over-the-air updates (`eas update`) β€” no local Xcode or Android Studio required. Run EAS CLI as `bunx eas-cli <command>` in Bun projects, or `npx eas-cli@latest <command>` otherwise; substitute that for bare `eas` in docs examples.
Docs: https://docs.expo.dev/eas/index.md

## Rules

- If `ios/` and `android/` directories do not exist, they are generated (Continuous Native Generation). Never create or edit them by hand β€” configure native behavior in `app.json` and config plugins.
- Expo Go only includes its bundled native modules. After adding a library with native code, the app needs a development build: `npx expo run:ios|android` locally, or `eas build --profile development`.
- Prefer recommended Expo modules over third-party libraries, and check your available skills before adding dependencies. Docs: https://docs.expo.dev/versions/latest/index.md
1 change: 0 additions & 1 deletion fixtures/expo-auth/CLAUDE.md

This file was deleted.

5 changes: 0 additions & 5 deletions fixtures/expo/.claude/settings.json

This file was deleted.

42 changes: 40 additions & 2 deletions fixtures/expo/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,41 @@
# Expo HAS CHANGED
This is an Expo/React Native mobile application. Prioritize mobile-first patterns, performance, and cross-platform compatibility.

Read the exact versioned docs at https://docs.expo.dev/versions/v55.0.0/ before writing any code.
## Expo has changed β€” do not trust your training data

Expo ships breaking changes every SDK release. APIs you remember are likely renamed, moved, or removed. Before writing any code that touches an Expo, EAS, or React Native API:

1. Read the major version of the `expo` package in `package.json`.
2. Fetch the matching versioned docs: `https://docs.expo.dev/versions/v<major>.0.0/`
3. For anything else, fetch https://docs.expo.dev/llms.txt β€” an index of all Expo docs with corrections to common LLM misconceptions. Follow its links to the specific page you need; never answer from memory.

## Commands

Use `bunx` instead of `npx` if the project uses bun (`bun.lock` present).

```bash
npx expo install <package> # ALWAYS use instead of npm/yarn/pnpm/bun add β€” resolves SDK-compatible versions
npx expo start # start the dev server
npx expo lint # lint
npx tsc --noEmit # typecheck
npx expo-doctor # diagnose dependency and config issues
npx expo install --fix # fix incompatible package versions
```

Run lint and typecheck before declaring any task done.

## Navigation & Routing

- Use **Expo Router** for all navigation. Routes live in `src/app/` β€” every file there is a screen, `_layout.tsx` files define navigators. Keep non-route code (components, hooks, utils) outside `src/app/`.
- Import `Link`, `router`, and `useLocalSearchParams` from `expo-router`.
- Docs: https://docs.expo.dev/router/introduction.md

## Building with EAS

Use EAS to build, sign, and submit the app in the cloud (`eas build`, `eas submit`) and to ship over-the-air updates (`eas update`) β€” no local Xcode or Android Studio required. Run EAS CLI as `bunx eas-cli <command>` in Bun projects, or `npx eas-cli@latest <command>` otherwise; substitute that for bare `eas` in docs examples.
Docs: https://docs.expo.dev/eas/index.md

## Rules

- If `ios/` and `android/` directories do not exist, they are generated (Continuous Native Generation). Never create or edit them by hand β€” configure native behavior in `app.json` and config plugins.
- Expo Go only includes its bundled native modules. After adding a library with native code, the app needs a development build: `npx expo run:ios|android` locally, or `eas build --profile development`.
- Prefer recommended Expo modules over third-party libraries, and check your available skills before adding dependencies. Docs: https://docs.expo.dev/versions/latest/index.md
1 change: 0 additions & 1 deletion fixtures/expo/CLAUDE.md

This file was deleted.

4 changes: 2 additions & 2 deletions fixtures/next-auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
"@tanstack/react-query": "5.95.2",
"better-auth": "1.7.1",
"class-variance-authority": "^0.7.1",
"cn": "^0.3.0",
"cn": "^0.4.0",
"convex": "1.44.0",
"hono": "4.12.9",
"kitcn": "workspace:*",
"lucide-react": "^1.46.0",
"lucide-react": "^1.49.0",
"next": "16.3.4",
"next-themes": "^0.4.6",
"react": "19.2.8",
Expand Down
4 changes: 2 additions & 2 deletions fixtures/next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
"@opentelemetry/api": "1.9.0",
"@tanstack/react-query": "5.95.2",
"class-variance-authority": "^0.7.1",
"cn": "^0.3.0",
"cn": "^0.4.0",
"convex": "1.44.0",
"hono": "4.12.9",
"kitcn": "workspace:*",
"lucide-react": "^1.46.0",
"lucide-react": "^1.49.0",
"next": "16.3.4",
"next-themes": "^0.4.6",
"react": "19.2.8",
Expand Down
4 changes: 2 additions & 2 deletions fixtures/start-auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
"@tanstack/router-plugin": "latest",
"better-auth": "1.7.1",
"class-variance-authority": "^0.7.1",
"cn": "^0.3.0",
"cn": "^0.4.0",
"convex": "1.44.0",
"hono": "4.12.9",
"kitcn": "workspace:*",
"lucide-react": "^1.46.0",
"lucide-react": "^1.49.0",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"shadcn": "latest",
Expand Down
4 changes: 2 additions & 2 deletions fixtures/start/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
"@tanstack/react-start": "latest",
"@tanstack/router-plugin": "latest",
"class-variance-authority": "^0.7.1",
"cn": "^0.3.0",
"cn": "^0.4.0",
"convex": "1.44.0",
"hono": "4.12.9",
"kitcn": "workspace:*",
"lucide-react": "^1.46.0",
"lucide-react": "^1.49.0",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"shadcn": "latest",
Expand Down
4 changes: 2 additions & 2 deletions fixtures/vite-auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
"@tanstack/react-query": "5.95.2",
"better-auth": "1.7.1",
"class-variance-authority": "^0.7.1",
"cn": "^0.3.0",
"cn": "^0.4.0",
"convex": "1.44.0",
"hono": "4.12.9",
"kitcn": "workspace:*",
"lucide-react": "^1.46.0",
"lucide-react": "^1.49.0",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"shadcn": "latest",
Expand Down
4 changes: 2 additions & 2 deletions fixtures/vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
"@tailwindcss/vite": "^4",
"@tanstack/react-query": "5.95.2",
"class-variance-authority": "^0.7.1",
"cn": "^0.3.0",
"cn": "^0.4.0",
"convex": "1.44.0",
"hono": "4.12.9",
"kitcn": "workspace:*",
"lucide-react": "^1.46.0",
"lucide-react": "^1.49.0",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"shadcn": "latest",
Expand Down
15 changes: 15 additions & 0 deletions packages/kitcn/skills/kitcn/references/features/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -406,11 +406,26 @@ All from `kitcn/react`:
authClient={authClient}
convexQueryClient={convexQueryClient} // when using TanStack Query
initialToken={token} // from SSR (caller.getToken())
optimisticAuth // opt in: open gates while Convex confirms a held JWT
onTokenIdentityChange={() => window.location.reload()}
onMutationUnauthorized={() => router.push('/login')}
onQueryUnauthorized={({ queryName }) => console.log(`Unauth: ${queryName}`)}
>
```

`optimisticAuth` only opens auth-bound query gates for a held, unexpired JWT;
expired, opaque, and refused tokens stay closed. Enable
`onTokenIdentityChange` to refuse a JWT whose `sub` or `sessionId` differs from
the document identity before Convex or HTTP sees it. The client closes before
the callback runs, so reload the document there.

For multiple provider mounts, pass `tokenIdentityBaseline` as
`sub|sessionId`, or a getter returning the document's current identity. The
getter is checked for every admission, cached tokens included.
`onTokenIdentityAdmitted(token)` observes admitted JWTs so the app can update
that shared baseline. All three identity options require
`onTokenIdentityChange`.

For `@convex-dev/auth` (React Native):
```tsx
import { ConvexProviderWithAuth } from 'kitcn/react';
Expand Down
16 changes: 16 additions & 0 deletions packages/kitcn/skills/kitcn/references/features/react.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,22 @@ const mutation = useMutation(crpc.user.update.mutationOptions({

Signature: `crpc.path.mutationOptions(options?)` β€” standard TanStack mutation options except `mutationFn`.

Pass `optimisticUpdate(localStore, args)` to use Convex's optimistic local
query store. This option is removed before the TanStack options are returned;
Convex replays it as query data changes and owns rollback when the mutation
completes.

```ts
const mutation = useMutation(crpc.todos.rename.mutationOptions({
optimisticUpdate: (store, args) => {
const todos = store.getQuery(api.todos.list, {});
if (todos) store.setQuery(api.todos.list, {}, todos.map((todo) =>
todo._id === args.id ? { ...todo, title: args.title } : todo
));
},
}));
```

### Mutation Keys

```ts
Expand Down
Loading
Loading