diff --git a/plugins/emulate/.agents/skills/github/SKILL.md b/plugins/emulate/.agents/skills/github/SKILL.md index da6f707e..39fd4e68 100644 --- a/plugins/emulate/.agents/skills/github/SKILL.md +++ b/plugins/emulate/.agents/skills/github/SKILL.md @@ -27,6 +27,32 @@ const github = await createEmulator({ service: 'github', port: 4001 }) // github.url === 'http://localhost:4001' ``` +For a programmatic GitHub App, omit `private_key` and read the generated RSA key from the instance: + +```typescript +const github = await createEmulator({ + service: 'github', + port: 4001, + seed: { + github: { + users: [{ login: 'octocat' }], + apps: [{ + app_id: 12345, + slug: 'my-github-app', + name: 'My GitHub App', + installations: [{ installation_id: 100, account: 'octocat' }], + }], + }, + }, +}) + +const privateKey = github.generatedSecrets.find( + secret => secret.kind === 'github.app_private_key' && secret.id === '12345', +)?.value +``` + +The key remains stable across `github.reset()`. Explicit keys are not included in `generatedSecrets`. CLI seed files still require `private_key`. + ## Auth Pass tokens as `Authorization: Bearer ` or `Authorization: token `. @@ -210,6 +236,9 @@ curl http://localhost:4001/user/emails -H "Authorization: Bearer $TOKEN" # Get repo curl http://localhost:4001/repos/octocat/hello-world +# Get repo by numeric ID +curl http://localhost:4001/repositories/1 + # Create user repo curl -X POST http://localhost:4001/user/repos \ -H "Authorization: Bearer $TOKEN" \ @@ -235,6 +264,27 @@ curl -X DELETE http://localhost:4001/repos/octocat/hello-world \ # Topics, languages, contributors, forks, collaborators, tags, transfer ``` +### Contents & Commit History + +```bash +# Read a file or list a directory at a branch, tag, or commit +curl "http://localhost:4001/repos/octocat/hello-world/contents/README.md?ref=main" + +# Download raw file content from the URL advertised by contents and commit responses +curl http://localhost:4001/octocat/hello-world/raw/main/README.md + +# Create or update a file and commit the change +curl -X PUT http://localhost:4001/repos/octocat/hello-world/contents/notes.txt \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"message": "Update notes", "content": "aGVsbG8K"}' + +# List commits, get a commit with file stats, or compare refs +curl http://localhost:4001/repos/octocat/hello-world/commits +curl http://localhost:4001/repos/octocat/hello-world/commits/main +curl http://localhost:4001/repos/octocat/hello-world/compare/v1.0.0...main +``` + ### Issues ```bash diff --git a/plugins/emulate/.agents/skills/stripe/SKILL.md b/plugins/emulate/.agents/skills/stripe/SKILL.md index 671fe535..932341fa 100644 --- a/plugins/emulate/.agents/skills/stripe/SKILL.md +++ b/plugins/emulate/.agents/skills/stripe/SKILL.md @@ -291,6 +291,7 @@ curl http://localhost:4000/v1/payment_methods ## Webhooks The emulator dispatches webhook events when state changes. Register webhooks via seed config or programmatically. +Webhooks configured with a `secret` include `Stripe-Signature: t=,v1=`. The signature is an HMAC SHA-256 over `.`. ### Events dispatched diff --git a/plugins/emulate/agent/skills/github/SKILL.md b/plugins/emulate/agent/skills/github/SKILL.md index 6b37f58b..cedf88f6 100644 --- a/plugins/emulate/agent/skills/github/SKILL.md +++ b/plugins/emulate/agent/skills/github/SKILL.md @@ -24,6 +24,32 @@ const github = await createEmulator({ service: 'github', port: 4001 }) // github.url === 'http://localhost:4001' ``` +For a programmatic GitHub App, omit `private_key` and read the generated RSA key from the instance: + +```typescript +const github = await createEmulator({ + service: 'github', + port: 4001, + seed: { + github: { + users: [{ login: 'octocat' }], + apps: [{ + app_id: 12345, + slug: 'my-github-app', + name: 'My GitHub App', + installations: [{ installation_id: 100, account: 'octocat' }], + }], + }, + }, +}) + +const privateKey = github.generatedSecrets.find( + secret => secret.kind === 'github.app_private_key' && secret.id === '12345', +)?.value +``` + +The key remains stable across `github.reset()`. Explicit keys are not included in `generatedSecrets`. CLI seed files still require `private_key`. + ## Auth Pass tokens as `Authorization: Bearer ` or `Authorization: token `. @@ -207,6 +233,9 @@ curl http://localhost:4001/user/emails -H "Authorization: Bearer $TOKEN" # Get repo curl http://localhost:4001/repos/octocat/hello-world +# Get repo by numeric ID +curl http://localhost:4001/repositories/1 + # Create user repo curl -X POST http://localhost:4001/user/repos \ -H "Authorization: Bearer $TOKEN" \ @@ -232,6 +261,27 @@ curl -X DELETE http://localhost:4001/repos/octocat/hello-world \ # Topics, languages, contributors, forks, collaborators, tags, transfer ``` +### Contents & Commit History + +```bash +# Read a file or list a directory at a branch, tag, or commit +curl "http://localhost:4001/repos/octocat/hello-world/contents/README.md?ref=main" + +# Download raw file content from the URL advertised by contents and commit responses +curl http://localhost:4001/octocat/hello-world/raw/main/README.md + +# Create or update a file and commit the change +curl -X PUT http://localhost:4001/repos/octocat/hello-world/contents/notes.txt \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"message": "Update notes", "content": "aGVsbG8K"}' + +# List commits, get a commit with file stats, or compare refs +curl http://localhost:4001/repos/octocat/hello-world/commits +curl http://localhost:4001/repos/octocat/hello-world/commits/main +curl http://localhost:4001/repos/octocat/hello-world/compare/v1.0.0...main +``` + ### Issues ```bash diff --git a/plugins/emulate/agent/skills/stripe/SKILL.md b/plugins/emulate/agent/skills/stripe/SKILL.md index 6f3f3e25..6fbcd3db 100644 --- a/plugins/emulate/agent/skills/stripe/SKILL.md +++ b/plugins/emulate/agent/skills/stripe/SKILL.md @@ -288,6 +288,7 @@ curl http://localhost:4000/v1/payment_methods ## Webhooks The emulator dispatches webhook events when state changes. Register webhooks via seed config or programmatically. +Webhooks configured with a `secret` include `Stripe-Signature: t=,v1=`. The signature is an HMAC SHA-256 over `.`. ### Events dispatched diff --git a/plugins/emulate/skills-lock.json b/plugins/emulate/skills-lock.json index 8f11ecad..d522bfd1 100644 --- a/plugins/emulate/skills-lock.json +++ b/plugins/emulate/skills-lock.json @@ -23,7 +23,7 @@ "source": "vercel-labs/emulate", "sourceType": "github", "skillPath": "skills/github/SKILL.md", - "computedHash": "9f35addc8b40f7e75804f07ec140044ab32cf00f9b4ef536d2d9c2e1a5b24a3c" + "computedHash": "cde1f1eb6bc92ccf36b9c1fe18039b3cb8f1a5008f5b3bce95052c31bb574063" }, "google": { "source": "vercel-labs/emulate", @@ -65,7 +65,7 @@ "source": "vercel-labs/emulate", "sourceType": "github", "skillPath": "skills/stripe/SKILL.md", - "computedHash": "406885aaa09f57d0a326d6af005e8cd432ea69d9fbbf07de5bda251cb92d8cd1" + "computedHash": "d2e822c16e3e7698b719e9dd06e21ed1019cc37a62e29c1b51718da838ee106d" }, "vercel": { "source": "vercel-labs/emulate", diff --git a/plugins/nuxt-ui/.agents/skills/nuxt-ui/SKILL.md b/plugins/nuxt-ui/.agents/skills/nuxt-ui/SKILL.md index 037e4946..2ce6e71f 100644 --- a/plugins/nuxt-ui/.agents/skills/nuxt-ui/SKILL.md +++ b/plugins/nuxt-ui/.agents/skills/nuxt-ui/SKILL.md @@ -24,12 +24,12 @@ claude mcp add --transport http nuxt-ui https://ui.nuxt.com/mcp ``` Key MCP tools: -- `search_components` — find components by name, description, or category (no params = list all) -- `search_composables` — find composables by name or description (no params = list all) -- `search_icons` — search Iconify icons (defaults to `lucide`), returns `i-{prefix}-{name}` names -- `get_component` — full component documentation with usage examples -- `get_component_metadata` — props, slots, events (lightweight, no docs content) -- `get_example` — real-world code examples +- `search-components` — find components by name, category, or intent (no params = list all) +- `search-composables` — find composables by name or description (no params = list all) +- `search-icons` — search Iconify icons (defaults to `lucide`), returns `i-{prefix}-{name}` names +- `get-component` — full component documentation with usage examples +- `get-component-metadata` — props, slots, events (lightweight, no docs content) +- `get-example` — real-world code examples When you need to know **what a component accepts** or **how its API works**, use the MCP. This skill teaches you **when to use which component** and **how to build well**. @@ -39,7 +39,7 @@ When you need to know **what a component accepts** or **how its API works**, use 2. **Always use semantic colors** — `text-default`, `bg-elevated`, `border-muted`, etc. Never use raw Tailwind palette colors like `text-gray-500`. 3. **Read generated theme files for slot names** — Nuxt: `.nuxt/ui/.ts`, Vue: `node_modules/.nuxt-ui/ui/.ts`. These show every slot, variant, and default class for any component. 4. **Override priority** (highest wins): `ui` prop / `class` prop → global config → theme defaults. -5. **Icons use `i-{collection}-{name}` format** — `lucide` is the default collection. Use the MCP `search_icons` tool to find icons, or browse at [icones.js.org](https://icones.js.org). +5. **Icons use `i-{collection}-{name}` format** — `lucide` is the default collection. Use the MCP `search-icons` tool to find icons, or browse at [icones.js.org](https://icones.js.org). ## How to use this skill diff --git a/plugins/nuxt-ui/.agents/skills/nuxt-ui/references/components.md b/plugins/nuxt-ui/.agents/skills/nuxt-ui/references/components.md index 4d34ef4a..e0ef7b5e 100644 --- a/plugins/nuxt-ui/.agents/skills/nuxt-ui/references/components.md +++ b/plugins/nuxt-ui/.agents/skills/nuxt-ui/references/components.md @@ -1,6 +1,6 @@ # Components -Quick-reference index of all 125+ components. For full API docs (props, slots, events, examples), use the MCP `get_component` or `get_component_metadata` tools. +Quick-reference index of all 125+ components. For full API docs (props, slots, events, examples), use the MCP `get-component` or `get-component-metadata` tools. ## Layout diff --git a/plugins/nuxt-ui/.agents/skills/nuxt-ui/references/guidelines/component-selection.md b/plugins/nuxt-ui/.agents/skills/nuxt-ui/references/guidelines/component-selection.md index 142a7a75..eb416714 100644 --- a/plugins/nuxt-ui/.agents/skills/nuxt-ui/references/guidelines/component-selection.md +++ b/plugins/nuxt-ui/.agents/skills/nuxt-ui/references/guidelines/component-selection.md @@ -1,6 +1,6 @@ # Component Selection -Decision matrices for choosing the right component. When in doubt, use the MCP `search_components` tool. +Decision matrices for choosing the right component. When in doubt, use the MCP `search-components` tool. ## Overlays diff --git a/plugins/nuxt-ui/.agents/skills/nuxt-ui/references/guidelines/conventions.md b/plugins/nuxt-ui/.agents/skills/nuxt-ui/references/guidelines/conventions.md index e63c5d28..c1aa29eb 100644 --- a/plugins/nuxt-ui/.agents/skills/nuxt-ui/references/guidelines/conventions.md +++ b/plugins/nuxt-ui/.agents/skills/nuxt-ui/references/guidelines/conventions.md @@ -365,6 +365,7 @@ npx nuxi@latest init -t ui/chat # AI chat (Vercel AI SDK) npx nuxi@latest init -t ui/editor # Rich text editor npx nuxi@latest init -t ui/portfolio # Portfolio npx nuxi@latest init -t ui/changelog # Changelog +npx nuxi@latest init -t ui/calendar # Calendar ``` ## Responsive patterns diff --git a/plugins/nuxt-ui/.agents/skills/nuxt-ui/references/guidelines/design-system.md b/plugins/nuxt-ui/.agents/skills/nuxt-ui/references/guidelines/design-system.md index 9392dd12..561dc411 100644 --- a/plugins/nuxt-ui/.agents/skills/nuxt-ui/references/guidelines/design-system.md +++ b/plugins/nuxt-ui/.agents/skills/nuxt-ui/references/guidelines/design-system.md @@ -207,7 +207,9 @@ Tailwind Variants uses `tailwind-merge` under the hood — conflicting classes a ### Replace instead of merge -Classes from the `ui` prop, the `class` prop, and global config are merged onto the component defaults. To replace a slot's defaults entirely instead, set it to a function in the `ui` prop or global config. It receives the resolved default classes as its argument, so you can reuse part of them. +Classes from the `ui` prop, the `class` prop, and global config are merged onto the component defaults. To replace them instead, set the slot to a function, which receives the default classes as its argument so you can reuse part of them. + +In global config it replaces the slot's own classes, so `variants` and `compoundVariants` still apply on top. In the `ui` and `class` props it runs after the variants, so it replaces the resolved classes, variants included. ```vue diff --git a/plugins/nuxt-ui/agent/skills/nuxt-ui/SKILL.md b/plugins/nuxt-ui/agent/skills/nuxt-ui/SKILL.md index e496c28a..735522de 100644 --- a/plugins/nuxt-ui/agent/skills/nuxt-ui/SKILL.md +++ b/plugins/nuxt-ui/agent/skills/nuxt-ui/SKILL.md @@ -22,12 +22,12 @@ claude mcp add --transport http nuxt-ui https://ui.nuxt.com/mcp ``` Key MCP tools: -- `search_components` — find components by name, description, or category (no params = list all) -- `search_composables` — find composables by name or description (no params = list all) -- `search_icons` — search Iconify icons (defaults to `lucide`), returns `i-{prefix}-{name}` names -- `get_component` — full component documentation with usage examples -- `get_component_metadata` — props, slots, events (lightweight, no docs content) -- `get_example` — real-world code examples +- `search-components` — find components by name, category, or intent (no params = list all) +- `search-composables` — find composables by name or description (no params = list all) +- `search-icons` — search Iconify icons (defaults to `lucide`), returns `i-{prefix}-{name}` names +- `get-component` — full component documentation with usage examples +- `get-component-metadata` — props, slots, events (lightweight, no docs content) +- `get-example` — real-world code examples When you need to know **what a component accepts** or **how its API works**, use the MCP. This skill teaches you **when to use which component** and **how to build well**. @@ -37,7 +37,7 @@ When you need to know **what a component accepts** or **how its API works**, use 2. **Always use semantic colors** — `text-default`, `bg-elevated`, `border-muted`, etc. Never use raw Tailwind palette colors like `text-gray-500`. 3. **Read generated theme files for slot names** — Nuxt: `.nuxt/ui/.ts`, Vue: `node_modules/.nuxt-ui/ui/.ts`. These show every slot, variant, and default class for any component. 4. **Override priority** (highest wins): `ui` prop / `class` prop → global config → theme defaults. -5. **Icons use `i-{collection}-{name}` format** — `lucide` is the default collection. Use the MCP `search_icons` tool to find icons, or browse at [icones.js.org](https://icones.js.org). +5. **Icons use `i-{collection}-{name}` format** — `lucide` is the default collection. Use the MCP `search-icons` tool to find icons, or browse at [icones.js.org](https://icones.js.org). ## How to use this skill diff --git a/plugins/nuxt-ui/agent/skills/nuxt-ui/references/components.md b/plugins/nuxt-ui/agent/skills/nuxt-ui/references/components.md index 4d34ef4a..e0ef7b5e 100644 --- a/plugins/nuxt-ui/agent/skills/nuxt-ui/references/components.md +++ b/plugins/nuxt-ui/agent/skills/nuxt-ui/references/components.md @@ -1,6 +1,6 @@ # Components -Quick-reference index of all 125+ components. For full API docs (props, slots, events, examples), use the MCP `get_component` or `get_component_metadata` tools. +Quick-reference index of all 125+ components. For full API docs (props, slots, events, examples), use the MCP `get-component` or `get-component-metadata` tools. ## Layout diff --git a/plugins/nuxt-ui/agent/skills/nuxt-ui/references/guidelines/component-selection.md b/plugins/nuxt-ui/agent/skills/nuxt-ui/references/guidelines/component-selection.md index 142a7a75..eb416714 100644 --- a/plugins/nuxt-ui/agent/skills/nuxt-ui/references/guidelines/component-selection.md +++ b/plugins/nuxt-ui/agent/skills/nuxt-ui/references/guidelines/component-selection.md @@ -1,6 +1,6 @@ # Component Selection -Decision matrices for choosing the right component. When in doubt, use the MCP `search_components` tool. +Decision matrices for choosing the right component. When in doubt, use the MCP `search-components` tool. ## Overlays diff --git a/plugins/nuxt-ui/agent/skills/nuxt-ui/references/guidelines/conventions.md b/plugins/nuxt-ui/agent/skills/nuxt-ui/references/guidelines/conventions.md index e63c5d28..c1aa29eb 100644 --- a/plugins/nuxt-ui/agent/skills/nuxt-ui/references/guidelines/conventions.md +++ b/plugins/nuxt-ui/agent/skills/nuxt-ui/references/guidelines/conventions.md @@ -365,6 +365,7 @@ npx nuxi@latest init -t ui/chat # AI chat (Vercel AI SDK) npx nuxi@latest init -t ui/editor # Rich text editor npx nuxi@latest init -t ui/portfolio # Portfolio npx nuxi@latest init -t ui/changelog # Changelog +npx nuxi@latest init -t ui/calendar # Calendar ``` ## Responsive patterns diff --git a/plugins/nuxt-ui/agent/skills/nuxt-ui/references/guidelines/design-system.md b/plugins/nuxt-ui/agent/skills/nuxt-ui/references/guidelines/design-system.md index 9392dd12..561dc411 100644 --- a/plugins/nuxt-ui/agent/skills/nuxt-ui/references/guidelines/design-system.md +++ b/plugins/nuxt-ui/agent/skills/nuxt-ui/references/guidelines/design-system.md @@ -207,7 +207,9 @@ Tailwind Variants uses `tailwind-merge` under the hood — conflicting classes a ### Replace instead of merge -Classes from the `ui` prop, the `class` prop, and global config are merged onto the component defaults. To replace a slot's defaults entirely instead, set it to a function in the `ui` prop or global config. It receives the resolved default classes as its argument, so you can reuse part of them. +Classes from the `ui` prop, the `class` prop, and global config are merged onto the component defaults. To replace them instead, set the slot to a function, which receives the default classes as its argument so you can reuse part of them. + +In global config it replaces the slot's own classes, so `variants` and `compoundVariants` still apply on top. In the `ui` and `class` props it runs after the variants, so it replaces the resolved classes, variants included. ```vue diff --git a/plugins/nuxt-ui/skills-lock.json b/plugins/nuxt-ui/skills-lock.json index dedff862..61c1e4f7 100644 --- a/plugins/nuxt-ui/skills-lock.json +++ b/plugins/nuxt-ui/skills-lock.json @@ -5,7 +5,7 @@ "source": "nuxt/ui", "sourceType": "github", "skillPath": "skills/nuxt-ui/SKILL.md", - "computedHash": "ef3a731fdfa8439d3c7f0c6d497a67073216b8cb7d7fa6a3c605a6fda4ede19a" + "computedHash": "e4701edeb717288700e311e74511a96d4a8a7e1c74fa0c1624b01379d91459f3" } } } diff --git a/plugins/portless/.agents/skills/portless/SKILL.md b/plugins/portless/.agents/skills/portless/SKILL.md index d386f2ff..1e528ee0 100644 --- a/plugins/portless/.agents/skills/portless/SKILL.md +++ b/plugins/portless/.agents/skills/portless/SKILL.md @@ -169,7 +169,7 @@ Use `portless proxy start --tld localhost --tld test` to serve the same app name TLDs can be multi-segment DNS names such as `dev.example.com`, so local URLs can mirror production structure (`myapp.dev.example.com`). Each label follows DNS rules: lowercase letters, digits, interior hyphens, 63 characters per label, 253 total. Strict OAuth providers that reject `.localhost` redirect URIs accept a real domain like `https://myapp.dev.example.com/api/auth/callback/google`. -Most frameworks (Next.js, Express, Nuxt, etc.) respect the `PORT` env var automatically. For frameworks that ignore `PORT` (Vite, VitePlus, Astro, React Router, Angular, Expo, React Native), portless auto-injects the correct `--port` flag and, when needed, a matching `--host` CLI flag. +Most frameworks (Next.js, Express, Nuxt, etc.) respect the `PORT` env var automatically. For frameworks that ignore `PORT` (Vite, VitePlus, Astro, React Router, Angular, Expo, React Native), portless auto-injects the correct `--port` flag and, when needed, a matching `--host` CLI flag. Injection reaches through a package script whose command starts with the framework or a known runner (`"dev": "vite"`, `"dev": "bunx vite"`). Only the framework's server commands get the flags (`dev`, `serve`, `preview`, `start`, a bare `vite`, or `vite [root]`); a command that does not serve, such as `vite build`, `vite optimize`, `vp test` or `astro check`, rejects them and is left alone. Expo connection modes (`--localhost`, `--lan`, `--tunnel`) are preserved while the assigned port is still injected. A script portless cannot classify is left alone too: a flag before the subcommand on a CLI whose flag grammar it does not track (`vp --mode dev build`). Portless also leaves a script alone when appending flags to it would not work: a compound command (`&&`, `|`, `;`), a trailing `#` comment, its own `--` option terminator, an env prefix (`NODE_ENV=production vite`), delegation to another script (`"dev": "npm run dev:vite"`), or runner flags before the script name (`bun run --bun dev`). Those keep their own port, so set it in the script yourself. ### State directory @@ -383,7 +383,7 @@ portless proxy start -p 8080 ### Framework not respecting PORT -Portless auto-injects the right `--port` flag and, when needed, a matching `--host` flag for frameworks that ignore the `PORT` env var: **Vite**, **VitePlus** (`vp`), **Astro**, **React Router**, **Angular**, **Expo**, and **React Native**. SvelteKit uses Vite internally and is handled automatically. +Portless auto-injects the right `--port` flag and, when needed, a matching `--host` flag for frameworks that ignore the `PORT` env var: **Vite**, **VitePlus** (`vp`), **Astro**, **React Router**, **Angular**, **Expo**, and **React Native**. SvelteKit uses Vite internally and is handled automatically. Injection reaches through a package script whose command starts with the framework or a known runner, and only for the framework's server commands (`dev`, `serve`, `preview`, `start`, or a bare `vite`) — `vite build`, `vite optimize`, `vp test` and other non-serving commands reject the flags, so they are left untouched, as is any invocation portless cannot classify (`vp --mode dev build`). It is also skipped for a compound command (`&&`, `|`, `;`), a trailing `#` comment, its own `--` option terminator, an env prefix (`NODE_ENV=production vite`), delegation to another script, and runner flags before the script name (`bun run --bun dev`) — each of those keeps its own port and the app returns 502, so set the port in the script yourself. For other frameworks that don't read `PORT`, pass the port manually: diff --git a/plugins/portless/agent/skills/portless/SKILL.md b/plugins/portless/agent/skills/portless/SKILL.md index 3b9f2302..dda0dda7 100644 --- a/plugins/portless/agent/skills/portless/SKILL.md +++ b/plugins/portless/agent/skills/portless/SKILL.md @@ -167,7 +167,7 @@ Use `portless proxy start --tld localhost --tld test` to serve the same app name TLDs can be multi-segment DNS names such as `dev.example.com`, so local URLs can mirror production structure (`myapp.dev.example.com`). Each label follows DNS rules: lowercase letters, digits, interior hyphens, 63 characters per label, 253 total. Strict OAuth providers that reject `.localhost` redirect URIs accept a real domain like `https://myapp.dev.example.com/api/auth/callback/google`. -Most frameworks (Next.js, Express, Nuxt, etc.) respect the `PORT` env var automatically. For frameworks that ignore `PORT` (Vite, VitePlus, Astro, React Router, Angular, Expo, React Native), portless auto-injects the correct `--port` flag and, when needed, a matching `--host` CLI flag. +Most frameworks (Next.js, Express, Nuxt, etc.) respect the `PORT` env var automatically. For frameworks that ignore `PORT` (Vite, VitePlus, Astro, React Router, Angular, Expo, React Native), portless auto-injects the correct `--port` flag and, when needed, a matching `--host` CLI flag. Injection reaches through a package script whose command starts with the framework or a known runner (`"dev": "vite"`, `"dev": "bunx vite"`). Only the framework's server commands get the flags (`dev`, `serve`, `preview`, `start`, a bare `vite`, or `vite [root]`); a command that does not serve, such as `vite build`, `vite optimize`, `vp test` or `astro check`, rejects them and is left alone. Expo connection modes (`--localhost`, `--lan`, `--tunnel`) are preserved while the assigned port is still injected. A script portless cannot classify is left alone too: a flag before the subcommand on a CLI whose flag grammar it does not track (`vp --mode dev build`). Portless also leaves a script alone when appending flags to it would not work: a compound command (`&&`, `|`, `;`), a trailing `#` comment, its own `--` option terminator, an env prefix (`NODE_ENV=production vite`), delegation to another script (`"dev": "npm run dev:vite"`), or runner flags before the script name (`bun run --bun dev`). Those keep their own port, so set it in the script yourself. ### State directory @@ -381,7 +381,7 @@ portless proxy start -p 8080 ### Framework not respecting PORT -Portless auto-injects the right `--port` flag and, when needed, a matching `--host` flag for frameworks that ignore the `PORT` env var: **Vite**, **VitePlus** (`vp`), **Astro**, **React Router**, **Angular**, **Expo**, and **React Native**. SvelteKit uses Vite internally and is handled automatically. +Portless auto-injects the right `--port` flag and, when needed, a matching `--host` flag for frameworks that ignore the `PORT` env var: **Vite**, **VitePlus** (`vp`), **Astro**, **React Router**, **Angular**, **Expo**, and **React Native**. SvelteKit uses Vite internally and is handled automatically. Injection reaches through a package script whose command starts with the framework or a known runner, and only for the framework's server commands (`dev`, `serve`, `preview`, `start`, or a bare `vite`) — `vite build`, `vite optimize`, `vp test` and other non-serving commands reject the flags, so they are left untouched, as is any invocation portless cannot classify (`vp --mode dev build`). It is also skipped for a compound command (`&&`, `|`, `;`), a trailing `#` comment, its own `--` option terminator, an env prefix (`NODE_ENV=production vite`), delegation to another script, and runner flags before the script name (`bun run --bun dev`) — each of those keeps its own port and the app returns 502, so set the port in the script yourself. For other frameworks that don't read `PORT`, pass the port manually: diff --git a/plugins/portless/skills-lock.json b/plugins/portless/skills-lock.json index 583f677b..54a02dd5 100644 --- a/plugins/portless/skills-lock.json +++ b/plugins/portless/skills-lock.json @@ -5,7 +5,7 @@ "source": "vercel-labs/portless", "sourceType": "github", "skillPath": "skills/portless/SKILL.md", - "computedHash": "5b758ea66233a1ebd494554904db55d902f5ea8832c353188d771f5c6fed9d2b" + "computedHash": "bf9e89110dfcda4ff080db44cbc13ed7891bb2a29f80fb84b461c7c3346013ec" } } } diff --git a/plugins/react-native/.agents/skills/vercel-react-native-skills/metadata.json b/plugins/react-native/.agents/skills/vercel-react-native-skills/metadata.json deleted file mode 100644 index 600eb5bc..00000000 --- a/plugins/react-native/.agents/skills/vercel-react-native-skills/metadata.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "version": "1.0.0", - "organization": "Engineering", - "date": "January 2026", - "abstract": "Comprehensive performance optimization guide for React Native applications, designed for AI agents and LLMs. Contains 35+ rules across 13 categories, prioritized by impact from critical (core rendering, list performance) to incremental (fonts, imports). Each rule includes detailed explanations, real-world examples comparing incorrect vs. correct implementations, and specific impact metrics to guide automated refactoring and code generation.", - "references": [ - "https://react.dev", - "https://reactnative.dev", - "https://docs.swmansion.com/react-native-reanimated", - "https://docs.swmansion.com/react-native-gesture-handler", - "https://docs.expo.dev", - "https://legendapp.com/open-source/legend-list", - "https://github.com/nandorojo/galeria", - "https://zeego.dev" - ] -} diff --git a/plugins/react-native/agent/skills/vercel-react-native-skills/metadata.json b/plugins/react-native/agent/skills/vercel-react-native-skills/metadata.json deleted file mode 100644 index 600eb5bc..00000000 --- a/plugins/react-native/agent/skills/vercel-react-native-skills/metadata.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "version": "1.0.0", - "organization": "Engineering", - "date": "January 2026", - "abstract": "Comprehensive performance optimization guide for React Native applications, designed for AI agents and LLMs. Contains 35+ rules across 13 categories, prioritized by impact from critical (core rendering, list performance) to incremental (fonts, imports). Each rule includes detailed explanations, real-world examples comparing incorrect vs. correct implementations, and specific impact metrics to guide automated refactoring and code generation.", - "references": [ - "https://react.dev", - "https://reactnative.dev", - "https://docs.swmansion.com/react-native-reanimated", - "https://docs.swmansion.com/react-native-gesture-handler", - "https://docs.expo.dev", - "https://legendapp.com/open-source/legend-list", - "https://github.com/nandorojo/galeria", - "https://zeego.dev" - ] -} diff --git a/plugins/react-native/skills-lock.json b/plugins/react-native/skills-lock.json index 14674bd4..8545c572 100644 --- a/plugins/react-native/skills-lock.json +++ b/plugins/react-native/skills-lock.json @@ -5,7 +5,7 @@ "source": "vercel-labs/agent-skills", "sourceType": "github", "skillPath": "skills/react-native-skills/SKILL.md", - "computedHash": "41d24eafa7c3d82e270439808f7cfbc4d51aeb2d14f2809a2267c16275784d06" + "computedHash": "2e9088a7333666d8c2833b8ff58bd51b955501c42b4c7244f72b4cbf22dafcc4" } } } diff --git a/plugins/react/.agents/skills/vercel-composition-patterns/metadata.json b/plugins/react/.agents/skills/vercel-composition-patterns/metadata.json deleted file mode 100644 index 3470b744..00000000 --- a/plugins/react/.agents/skills/vercel-composition-patterns/metadata.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": "1.0.0", - "organization": "Engineering", - "date": "January 2026", - "abstract": "Composition patterns for building flexible, maintainable React components. Avoid boolean prop proliferation by using compound components, lifting state, and composing internals. These patterns make codebases easier for both humans and AI agents to work with as they scale.", - "references": [ - "https://react.dev", - "https://react.dev/learn/passing-data-deeply-with-context", - "https://react.dev/reference/react/use" - ] -} diff --git a/plugins/react/.agents/skills/vercel-react-best-practices/metadata.json b/plugins/react/.agents/skills/vercel-react-best-practices/metadata.json deleted file mode 100644 index 3bec38b1..00000000 --- a/plugins/react/.agents/skills/vercel-react-best-practices/metadata.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "version": "1.0.0", - "organization": "Vercel Engineering", - "date": "January 2026", - "abstract": "Comprehensive performance optimization guide for React and Next.js applications, designed for AI agents and LLMs. Contains 40+ rules across 8 categories, prioritized by impact from critical (eliminating waterfalls, reducing bundle size) to incremental (advanced patterns). Each rule includes detailed explanations, real-world examples comparing incorrect vs. correct implementations, and specific impact metrics to guide automated refactoring and code generation.", - "references": [ - "https://react.dev", - "https://nextjs.org", - "https://swr.vercel.app", - "https://github.com/shuding/better-all", - "https://github.com/isaacs/node-lru-cache", - "https://vercel.com/blog/how-we-optimized-package-imports-in-next-js", - "https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast" - ] -} diff --git a/plugins/react/.agents/skills/vercel-react-view-transitions/README.md b/plugins/react/.agents/skills/vercel-react-view-transitions/README.md index 9f29bed9..061e32ae 100644 --- a/plugins/react/.agents/skills/vercel-react-view-transitions/README.md +++ b/plugins/react/.agents/skills/vercel-react-view-transitions/README.md @@ -9,7 +9,7 @@ An agent skill for implementing smooth, native-feeling animations using React's - **Shared element transitions** — morphing elements across different views - **View Transition Events** — imperative JavaScript animations via the Web Animations API - **CSS pseudo-elements** — `::view-transition-old`, `::view-transition-new`, `::view-transition-group` -- **Next.js integration** — `experimental.viewTransition`, the `transitionTypes` prop on `next/link`, App Router patterns +- **Next.js integration** — `experimental.viewTransition`, `transitionTypes` on `next/link` and `useRouter`, App Router patterns - **Accessibility** — `prefers-reduced-motion` handling - **Ready-to-use CSS recipes** — fade, slide, scale, directional navigation @@ -31,12 +31,13 @@ react-view-transitions/ Install via [skills.sh](https://skills.sh): ```bash -npx skills install https://github.com/vercel-labs/react-view-transitions-skill +npx skills add https://github.com/vercel-labs/agent-skills --skill vercel-react-view-transitions ``` ## Resources - [React `` docs](https://react.dev/reference/react/ViewTransition) - [React `addTransitionType` docs](https://react.dev/reference/react/addTransitionType) +- [Next.js View Transitions guide](https://nextjs.org/docs/app/guides/view-transitions) - [Next.js `viewTransition` config](https://nextjs.org/docs/app/api-reference/config/next-config-js/viewTransition) - [Next.js App Router Playground (view transitions)](https://github.com/vercel/next-app-router-playground/tree/main/app/view-transitions) — Vercel's reference implementation diff --git a/plugins/react/.agents/skills/vercel-react-view-transitions/SKILL.md b/plugins/react/.agents/skills/vercel-react-view-transitions/SKILL.md index dcecd8e3..1259682e 100644 --- a/plugins/react/.agents/skills/vercel-react-view-transitions/SKILL.md +++ b/plugins/react/.agents/skills/vercel-react-view-transitions/SKILL.md @@ -23,7 +23,7 @@ Implement **all** applicable patterns from this list, in this order: | 2 | **Suspense reveal** | "Data loaded" | | 3 | **List identity** (per-item `key`) | "Same items, new arrangement" | | 4 | **State change** (`enter`/`exit`) | "Something appeared/disappeared" | -| 5 | **Route change** (layout-level) | "Going to a new place" | +| 5 | **Route change** (page-level) | "Going to a new place" | This is an implementation order, not a "pick one" list. Implement every pattern that fits the app. Only skip a pattern if the app has no use case for it. @@ -44,13 +44,13 @@ Reserve directional slides for hierarchical navigation (list → detail) and ord - **Next.js:** Do **not** install `react@canary` — the App Router already bundles React canary internally. `ViewTransition` works out of the box. `npm ls react` may show a stable-looking version; this is expected. - **Without Next.js:** Install `react@canary react-dom@canary` (`ViewTransition` is not in stable React). -- Browser support: Chromium 111+, Firefox 144+, Safari 18.2+. Graceful degradation on unsupported browsers. +- Browser support: Chromium 125+ (React needs the v2 object form of `startViewTransition`), Firefox 144+, Safari 18.2+. Graceful degradation on unsupported browsers. --- ## Implementation Workflow -When adding view transitions to an existing app, **follow `references/implementation.md` step by step.** Start with the audit — do not skip it. Copy the CSS recipes from `references/css-recipes.md` into the global stylesheet — do not write your own animation CSS. +When adding view transitions to an existing app, **follow [references/implementation.md](references/implementation.md) step by step.** Start with the audit — do not skip it. Copy the CSS recipes from [references/css-recipes.md](references/css-recipes.md) into the global stylesheet — do not write your own animation CSS. --- @@ -74,7 +74,7 @@ React auto-assigns a unique `view-transition-name` and calls `document.startView |---------|--------------| | **enter** | `` first inserted during a Transition | | **exit** | `` first removed during a Transition | -| **update** | DOM mutations inside a ``. With nested VTs, mutation applies to the innermost one | +| **update** | DOM mutations inside a ``, or the boundary itself changing size/position due to an immediate sibling. With nested VTs, mutation applies to the innermost one | | **share** | Named VT unmounts and another with same `name` mounts in the same Transition | Only `startTransition`, `useDeferredValue`, or `Suspense` activate VTs. Regular `setState` does not animate. @@ -118,7 +118,7 @@ If `default` is `"none"`, all triggers are off unless explicitly listed. - `::view-transition-group(.class)` — container - `::view-transition-image-pair(.class)` — old + new pair -See `references/css-recipes.md` for ready-to-use animation recipes. +See [references/css-recipes.md](references/css-recipes.md) for ready-to-use animation recipes. --- @@ -177,12 +177,16 @@ export function DirectionalTransition({ children }: { children: React.ReactNode ### `router.back()` and Browser Back Button -`router.back()` and the browser's back/forward buttons do **not** trigger view transitions (`popstate` is synchronous, incompatible with `startViewTransition`). Use `router.push()` with an explicit URL instead. +`router.back()` and the browser's back/forward buttons carry **no transition types**, so type-keyed animations (directional slides) resolve to their `default` and don't play — untyped shared-element morphs still apply. For typed animations, use `router.push()` with an explicit URL. ### Types and Suspense Types are available during navigation but **not** during subsequent Suspense reveals (separate transitions, no type). Use type maps for page-level enter/exit; use simple string props for Suspense reveals. +### Shared Element Readiness + +A shared element transition can pair elements only when both the old and new views are rendered in the same Transition. If incoming content suspends, only its fallback exists for that update; the resolved content appears in a later Suspense transition and can be animated separately. + --- ## Shared Element Transitions @@ -202,6 +206,7 @@ Same `name` on two VTs — one unmounting, one mounting — creates a shared ele - Only one VT with a given `name` can be mounted at a time — use unique names (`photo-${id}`). Watch for reusable components: if a component with a named VT is rendered in both a modal/popover *and* a page, both mount simultaneously and break the morph. Either make the name conditional (via a prop) or move the named VT out of the shared component into the specific consumer. - `share` takes precedence over `enter`/`exit`. Think through each navigation path: when no matching pair forms (e.g., the target page doesn't have the same name), `enter`/`exit` fires instead. Consider whether the element needs a fallback animation for those paths. +- Two ways a wired-up morph silently never fires: (1) `default="none"` with no explicit `share` prop — share resolves to none; (2) type-keyed `share` where the navigation never adds the type — a plain link click resolves the map's `default`. Every link that should morph must add the type (`transitionTypes` on `next/link`, or `addTransitionType`). - Never use a fade-out exit on pages with shared morphs — use a directional slide instead. --- @@ -226,6 +231,10 @@ Same `name` on two VTs — one unmounting, one mounting — creates a shared ele Trigger inside `startTransition`. Avoid wrapper `
`s between list and VT. +### Layout Displacement Morph + +Only content inside an activated boundary animates position — everything else teleports to its new layout spot. Wrap the sibling content below a growing/shrinking list in a bare `` so it glides instead of jumping. See [Layout Displacement Morph](references/patterns.md#layout-displacement-morph). + ### Composing Shared Elements with List Identity Shared elements and list identity are independent concerns — don't confuse one for the other. When a list item contains a shared element (e.g., an image that morphs into a detail view), use two nested `` boundaries: @@ -271,7 +280,7 @@ Directional reveal: ``` -For more patterns, see `references/patterns.md`. +For more patterns, see [references/patterns.md](references/patterns.md). --- @@ -279,9 +288,11 @@ For more patterns, see `references/patterns.md`. Every VT matching the trigger fires simultaneously in a single `document.startViewTransition`. VTs in **different** transitions (navigation vs later Suspense resolve) don't compete. -### Use `default="none"` Liberally +### Use `default="none"` Deliberately + +Without it, every VT fires the browser cross-fade on **every** transition — Suspense resolves, `useDeferredValue` updates, background revalidations. Use `default="none"` on named/shared elements and type-keyed page VTs. -Without it, every VT fires the browser cross-fade on **every** transition — Suspense resolves, `useDeferredValue` updates, background revalidations. Always use `default="none"` and explicitly enable only desired triggers. +But it also turns off `update` (layout/reflow morphs) and `share` (a named pair with no explicit `share` prop never morphs). Keyed list items and displaced siblings *want* update — leave them bare or set `update="auto"`. ### Two Patterns Coexist @@ -292,28 +303,28 @@ They coexist because they fire at different moments. `default="none"` on both pr ### Nested VT Limitation -When a parent VT exits, nested VTs inside it do **not** fire their own enter/exit — only the outermost VT animates. Per-item staggered animations during page navigation are not possible today. See [react#36135](https://github.com/facebook/react/pull/36135) for an experimental opt-in fix. +When a parent VT mounts/unmounts **as one unit** with nested VTs inside it, the nested ones do not fire their own enter/exit — only the outermost VT animates. (A child VT mounted inside a *persistent* parent VT fires enter/exit normally.) Per-item staggered animations during page navigation are not possible today; the experimental opt-in is the `parentEnter`/`parentExit` props ([react#36690](https://github.com/facebook/react/pull/36690), experimental channel only). --- ## Next.js Integration -For Next.js setup (`experimental.viewTransition` flag, `transitionTypes` prop on `next/link`, App Router patterns, Server Components), see `references/nextjs.md`. +For Next.js setup (`experimental.viewTransition` flag, `transitionTypes` on `next/link` and `useRouter`, App Router patterns, Server Components), see [references/nextjs.md](references/nextjs.md). --- ## Accessibility -Always add the reduced motion CSS from `references/css-recipes.md` to your global stylesheet. +Always add the reduced motion CSS from [references/css-recipes.md](references/css-recipes.md#reduced-motion) to your global stylesheet. --- ## Reference Files -- **`references/implementation.md`** — Step-by-step implementation workflow. -- **`references/patterns.md`** — Patterns, animation timing, events API, troubleshooting. -- **`references/css-recipes.md`** — Ready-to-use CSS animation recipes. -- **`references/nextjs.md`** — Next.js App Router patterns and Server Component details. +- **[references/implementation.md](references/implementation.md)** — Step-by-step implementation workflow. +- **[references/patterns.md](references/patterns.md)** — Patterns, animation timing, events API, troubleshooting. +- **[references/css-recipes.md](references/css-recipes.md)** — Ready-to-use CSS animation recipes. +- **[references/nextjs.md](references/nextjs.md)** — Next.js App Router patterns and Server Component details. ## Full Compiled Document diff --git a/plugins/react/.agents/skills/vercel-react-view-transitions/metadata.json b/plugins/react/.agents/skills/vercel-react-view-transitions/metadata.json deleted file mode 100644 index aabe3e14..00000000 --- a/plugins/react/.agents/skills/vercel-react-view-transitions/metadata.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "version": "1.0.0", - "organization": "Vercel Engineering", - "date": "March 2026", - "abstract": "Guide for implementing smooth, native-feeling animations using React's View Transition API. Covers the component, addTransitionType, CSS view transition pseudo-elements, shared element transitions, JavaScript animations via Web Animations API, and Next.js integration including the transitionTypes prop on next/link. Includes ready-to-use CSS animation recipes and real-world patterns from production Next.js apps.", - "references": [ - "https://react.dev/reference/react/ViewTransition", - "https://react.dev/reference/react/addTransitionType", - "https://nextjs.org/docs/app/api-reference/config/next-config-js/viewTransition", - "https://github.com/vercel/next-app-router-playground/tree/main/app/view-transitions" - ] -} diff --git a/plugins/react/.agents/skills/vercel-react-view-transitions/references/css-recipes.md b/plugins/react/.agents/skills/vercel-react-view-transitions/references/css-recipes.md index f9c1fca5..8cd15783 100644 --- a/plugins/react/.agents/skills/vercel-react-view-transitions/references/css-recipes.md +++ b/plugins/react/.agents/skills/vercel-react-view-transitions/references/css-recipes.md @@ -204,6 +204,39 @@ Usage: `` --- +## Interactivity During Transitions + +The `::view-transition` overlay captures all pointer events. React shrinks it to zero when the root group doesn't animate, but in-flight animations still block clicks. To pass clicks/hover through even while animating: + +```css +::view-transition { + pointer-events: none; +} +``` + +Trade-offs: clicks can hit live elements under still-moving snapshots, and it only helps **unnamed** content — named participants are skipped by hit-testing for the transition's duration, no CSS override ([csswg#10930](https://github.com/w3c/csswg-drafts/issues/10930)). Weigh that before naming interactive elements; portal named popovers (see [Isolate Elements from Parent Animations](patterns.md#isolate-elements-from-parent-animations)). + +--- + +## No Root Cross-Fade (Live Root) + +The root cross-fades on every transition, freezing unnamed content behind a stale snapshot — hover and active styles stop rendering until it settles. `::view-transition-new(root)` is a **live** capture, so disabling the root animation keeps unnamed regions rendering (and, with the `pointer-events` recipe above, interactive): + +```css +::view-transition-old(root) { + display: none; +} +::view-transition-new(root) { + animation: none; +} +``` + +Named and classed groups still animate — they stack above root. Trade-off: unnamed content swaps instantly, so regions that should fade need their own VT. This also removes the main reason to hand-name static chrome; keep names only for elements that must stack above animating groups. + +Pairs well with enter-only reveals: skip the fallback-exit VT entirely (`` around the content, nothing on the skeleton) — the skeleton snaps out live while the content fades in. + +--- + ## Persistent Element Isolation ```css @@ -213,6 +246,8 @@ Usage: `` } ``` +Layer multiple pinned groups with z-index tiers — chrome at `100`, toasts/overlays that must beat everything at `200`. + ### Backdrop-Blur Workaround For elements with `backdrop-filter`, hide the old snapshot to avoid flash: @@ -226,6 +261,37 @@ For elements with `backdrop-filter`, hide the old snapshot to avoid flash: } ``` +### Floating Element Isolation (popovers, menus, tooltips, control clusters) + +Same freeze as persistent chrome. A floating/interactive element left rendered while a background transition runs is otherwise captured in the `root` snapshot and flickers as it settles. Give it a real, unique `view-transition-name` (never `none` — that's the CSS default = no isolation) and: + +```css +::view-transition-group(popover) { + animation: none; + z-index: 100; +} +::view-transition-old(popover), +::view-transition-new(popover) { + animation: none; +} +``` + +### Sliding Indicator (tab underline / segmented pill) + +One shared-name indicator morphs between positions. Slide the group; disable old/new so the solid bar slides instead of cross-fading: + +```css +::view-transition-group(.tab-underline) { + animation-duration: 220ms; + animation-timing-function: cubic-bezier(0.5, 0, 0.2, 1); +} +::view-transition-old(.tab-underline), +::view-transition-new(.tab-underline) { + animation: none; + height: 100%; +} +``` + --- ## Reduced Motion diff --git a/plugins/react/.agents/skills/vercel-react-view-transitions/references/implementation.md b/plugins/react/.agents/skills/vercel-react-view-transitions/references/implementation.md index e4207c9b..4d93bfcb 100644 --- a/plugins/react/.agents/skills/vercel-react-view-transitions/references/implementation.md +++ b/plugins/react/.agents/skills/vercel-react-view-transitions/references/implementation.md @@ -29,7 +29,7 @@ For each shared element (`name` prop), note every navigation where a pair forms ## Step 2: Add CSS Recipes -Copy the **complete** CSS recipe set from `css-recipes.md` into your global stylesheet. This includes timing variables, shared keyframes, fade, slide (vertical), directional navigation (forward/back), shared element morph, persistent element isolation, and reduced motion. +Copy the **complete** CSS recipe set from [css-recipes.md](css-recipes.md) into your global stylesheet. This includes timing variables, shared keyframes, fade, slide (vertical), directional navigation (forward/back), shared element morph, persistent element isolation, and reduced motion. Do not write your own animation CSS — the recipes handle staggered timing, motion blur on morphs, and reduced motion that are easy to get wrong. You can customize timing variables (`--duration-exit`, `--duration-enter`, `--duration-move`) after the initial setup. @@ -41,7 +41,7 @@ For every persistent element identified in Step 1, add a `viewTransitionName` st
...
``` -Then add the persistent element isolation CSS from `css-recipes.md` (prevents the element from animating during page transitions). If the element uses `backdrop-blur` or `backdrop-filter`, use the backdrop-blur workaround from `css-recipes.md` instead. +Then add the [Persistent Element Isolation](css-recipes.md#persistent-element-isolation) CSS (prevents the element from animating during page transitions). If the element uses `backdrop-blur` or `backdrop-filter`, use the [Backdrop-Blur Workaround](css-recipes.md#backdrop-blur-workaround) instead. If a Suspense fallback mirrors a persistent control (e.g., a skeleton search input), give both the real control and the skeleton the same `viewTransitionName` so they morph in place. @@ -76,7 +76,7 @@ Then wrap each **page component** (not layout) in a type-keyed ` ``` -The `nav-forward` and `nav-back` CSS classes from `css-recipes.md` produce horizontal slides. For simpler apps where directional motion isn't needed, a bare `` wrapper with `enter="fade-in"` / `exit="fade-out"` works too. +The `nav-forward` and `nav-back` CSS classes from [Directional Navigation](css-recipes.md#directional-navigation) produce horizontal slides. For simpler apps where directional motion isn't needed, a bare `` wrapper with `enter="fade-in"` / `exit="fade-out"` works too. Extract this into a reusable component so every page doesn't repeat the verbose type map: @@ -125,6 +125,7 @@ This example uses `slide-down` / `slide-up` for directional vertical motion. For **Rules:** - Always use `default="none"` on the content `` to prevent re-animation on revalidation or unrelated transitions. - Use simple string props (not type maps) on Suspense ``s — Suspense resolves fire as separate transitions with no type, so type-keyed props won't match. +- If the same element appears in **both** the fallback and the content (a title, a heading), it flickers on reveal — an opacity dip. Render it **outside** the `` boundary (or pin it), so it isn't in both. See [Suspense reveal flicker](patterns.md#suspense-reveal-flicker). ## Step 6: Add Shared Element Transitions @@ -142,13 +143,14 @@ For every shared visual element identified in Step 1, add matching named ` ``` -The `share="morph"` class uses the morph recipe from `css-recipes.md` (controlled duration + motion blur). For a simpler cross-fade, use `share="auto"` (browser default). +The `share="morph"` class uses the [Shared Element Morph](css-recipes.md#shared-element-morph) recipe (controlled duration + motion blur). For a simpler cross-fade, use `share="auto"` (browser default). -When list items contain shared elements, compose both patterns with two nested `` layers — see "Composing Shared Elements with List Identity" in `SKILL.md`. +When list items contain shared elements, compose both patterns with two nested `` layers — see [Composing Shared Elements with List Identity](../SKILL.md#composing-shared-elements-with-list-identity). **Rules:** - Names must be globally unique — use prefixes like `photo-${id}`. - Add `default="none"` on list-side shared elements to prevent per-item cross-fades on filter/search updates. +- The target must be **in the DOM at navigation time** for the pair to form. If it's behind a Suspense fallback (not rendered yet), no pair forms and it won't morph. It works when the target is present at the snapshot — render it above the data boundary, or have its data **cached/prefetched** so it resolves in time. ## Step 7: Verify Each Navigation Path @@ -169,14 +171,14 @@ If any path produces no animation or competing animations, revisit the relevant - **Bare `` without props** — without `default="none"`, it fires the browser's default cross-fade on every transition (every navigation, every Suspense resolve, every revalidation). Always set `default="none"` and explicitly enable only the triggers you want. - **Directional `` in a layout** — layouts persist across navigations and never unmount/remount. `enter`/`exit` props won't fire on route changes. Place the outer type-keyed `` in each page component. - **Fade-out exit with shared element morphs** — the page dissolving conflicts with the morph. Use a directional slide exit instead. -- **Writing custom animation CSS** — the recipes in `css-recipes.md` handle staggered timing, motion blur on morphs, and reduced motion. Copy them; don't reinvent them. +- **Writing custom animation CSS** — the recipes in [css-recipes.md](css-recipes.md) handle staggered timing, motion blur on morphs, and reduced motion. Copy them; don't reinvent them. - **Missing `default: "none"` in type-keyed objects** — TypeScript requires a `default` key, and without it the fallback is `"auto"` which fires on every transition. - **Type maps on Suspense reveals** — Suspense resolves fire as separate transitions with no type. Type-keyed props won't match — use simple string props instead. - **Raw `viewTransitionName` CSS to trigger animations** — React only calls `document.startViewTransition` when `` components are in the tree. A bare `viewTransitionName` style is for isolating elements from a parent's snapshot, not for triggering animations. - **`update` trigger for same-route navigations** — nested VTs inside the content steal the mutation from the parent, so `update` never fires on the outer VT. Use `key` + `name` + `share` instead. - **Named VT in a reusable component** — if a component with a named VT is rendered in both a modal/popover *and* a page, both mount simultaneously and break the morph. Make the name conditional or move it to the specific consumer. -- **`router.back()` for back navigation** — `router.back()` triggers synchronous `popstate`, incompatible with view transitions. Use `router.push()` with an explicit URL. +- **`router.back()` for back navigation** — traversals carry no transition types, so type-keyed animations don't play (untyped morphs still can). Use `router.push()` with an explicit URL for a fully animated back affordance. --- -For Next.js-specific implementation steps (config flag, `transitionTypes` on ``, same-route dynamic segments), see `nextjs.md`. +For Next.js-specific implementation steps (config flag, `transitionTypes` on ``, same-route dynamic segments), see [nextjs.md](nextjs.md). diff --git a/plugins/react/.agents/skills/vercel-react-view-transitions/references/nextjs.md b/plugins/react/.agents/skills/vercel-react-view-transitions/references/nextjs.md index 06a96c7e..dd42fd8d 100644 --- a/plugins/react/.agents/skills/vercel-react-view-transitions/references/nextjs.md +++ b/plugins/react/.agents/skills/vercel-react-view-transitions/references/nextjs.md @@ -2,7 +2,7 @@ ## Setup -`` works out of the box for `startTransition`/`Suspense` updates. To also animate `` navigations: +`` works out of the box — the bundled React canary ships it, and every `` navigation runs inside `React.startTransition`, so react-dom starts a view transition whenever affected `` components exist. Set the documented flag: ```js // next.config.js @@ -12,21 +12,23 @@ const nextConfig = { module.exports = nextConfig; ``` -This wraps every `` navigation in `document.startViewTransition`. Any VT with `default="auto"` fires on **every** link click — use `default="none"` to prevent competing animations. +(Historically the flag switched React to the experimental channel — required before `ViewTransition` reached canary. It no longer does; the experimental channel is only needed for `useSwipeTransition` gestures and `parentEnter`/`parentExit`, selected by other flags like `gestureTransition`.) -Do **not** install `react@canary` — see SKILL.md "Availability" for details. +Because every link click is a transition, any VT with `default="auto"` fires on **every** navigation — use `default="none"` to prevent competing animations. + +Do **not** install `react@canary` — see [Availability](../SKILL.md#availability) for details. --- ## Next.js Implementation Additions -When following `implementation.md`, apply these additions: +When following [implementation.md](implementation.md), apply these additions: **After Step 2:** Enable the experimental flag above. -**Step 4:** Use `transitionTypes` on `` — see "The `transitionTypes` Prop" section below for usage and availability. +**Step 4:** Use `transitionTypes` on `` — see [The `transitionTypes` Prop](#the-transitiontypes-prop-on-nextlink). If the animation depends on dynamic destination content, also see [When Content Must Be Ready](#when-content-must-be-ready). -**After Step 6:** For same-route dynamic segments (e.g., `/collection/[slug]`), use the `key` + `name` + `share` pattern — see Same-Route Dynamic Segment Transitions below. +**After Step 6:** For same-route dynamic segments (e.g., `/collection/[slug]`), use the `key` + `name` + `share` pattern — see [Same-Route Dynamic Segment Transitions](#same-route-dynamic-segment-transitions). --- @@ -36,7 +38,7 @@ When following `implementation.md`, apply these additions: A bare `` in layout works only if pages have **no** VTs of their own. -**Layouts persist across navigations** — `enter`/`exit` only fire on initial mount, not on route changes. Don't use type-keyed maps in layouts. +**Layouts persist across navigations** — `enter`/`exit` only fire on initial mount, not on route changes. Don't use type-keyed maps in layouts. Because layouts persist, chrome hosted in one (nav, sidebar, player) keeps its state across navigations for free — no `Activity` needed. Reserve `Activity` for in-page show/hide (see [Composing with Activity](patterns.md#composing-with-activity)). --- @@ -45,12 +47,34 @@ A bare `` in layout works only if pages have **no** VTs of their No wrapper component needed, works in Server Components: ```tsx -View Product + + View Product + ``` Replaces the manual pattern of `onNavigate` + `startTransition` + `addTransitionType` + `router.push()`. Reserve manual `startTransition` for non-link interactions (buttons, forms). -**Availability:** `transitionTypes` requires `experimental.viewTransition: true` and is available in Next.js 15+ canary builds and Next.js 16+. If unavailable, use `startTransition` + `addTransitionType` + `router.push()` (see Programmatic Navigation below). To check: `grep -r "transitionTypes" node_modules/next/dist/` — if no results, fall back to programmatic navigation. +**Availability:** `transitionTypes` shipped in **Next.js 16.2.0** (it is not gated on the `experimental.viewTransition` flag). If unavailable, use `startTransition` + `addTransitionType` + `router.push()` (see [Programmatic Navigation](#programmatic-navigation)). To check: `grep -r "transitionTypes" node_modules/next/dist/` — if no results, fall back to programmatic navigation. + +--- + +## When Content Must Be Ready + +A page transition can animate whatever Next.js renders during navigation, including a loading fallback. A shared content-to-content morph only works when the incoming content is ready as the navigation commits; content that has not rendered yet cannot form the incoming half of the pair. + +When an animation depends on dynamic destination content, use Next.js prefetching and caching to make that content available ahead of time. `` automatically prefetches in production, but the default behavior for dynamic routes may only prefetch a shell or loading boundary. Set `prefetch={true}` to prefetch the full route, and cache the data needed to render the shared content. + +```tsx + + Next + +``` + +With Cache Components, put reusable route data in a cached scope such as `use cache` so prefetching can include it. If the destination remains behind an unresolved Suspense boundary, the route transition animates the fallback instead. The content resolves in a separate Suspense transition without the original `nav-forward` or `nav-back` type, so give that content its own reveal animation when needed. + +Verify directional transitions in a production build with a cold client cache. Development mode does not run automatic `` prefetching. + +See the Next.js [View Transitions guide](https://nextjs.org/docs/app/guides/view-transitions) and [Prefetching guide](https://nextjs.org/docs/app/guides/prefetching). --- @@ -60,17 +84,20 @@ Replaces the manual pattern of `onNavigate` + `startTransition` + `addTransition 'use client'; import { useRouter } from 'next/navigation'; -import { startTransition, addTransitionType } from 'react'; -function handleNavigate(href: string) { +function DetailButton({ href }: { href: string }) { const router = useRouter(); - startTransition(() => { - addTransitionType('nav-forward'); - router.push(href); - }); + + return ( + + ); } ``` +The `transitionTypes` option adds the types inside the router's navigation Transition. Use `startTransition` + `addTransitionType` for non-navigation state updates, or as a fallback on Next.js versions without the router option. + --- ## Server-Side Filtering with `router.replace` @@ -91,7 +118,40 @@ function handleSort(sort: string) { } ``` -List items wrapped in `` will animate reorder. This is the server-component alternative to the client-side `useDeferredValue` pattern in `patterns.md`. +List items wrapped in `` will animate reorder. This is the server-component alternative to the client-side [Searchable Grid](patterns.md#searchable-grid-with-usedeferredvalue) pattern. + +--- + +## Routing-Driven Tabs + +The generalized sliding indicator ([Sliding Indicator](patterns.md#sliding-indicator-tabs)) driven by navigation instead of local state: tabs are ``s, `active` comes from the URL (a server prop), and `useOptimistic` slides the indicator instantly while the route commits. Key the mounted indicator to committed `active` so the bar lands where navigation actually settles. + +```tsx +'use client'; +import Link from 'next/link'; +import { useOptimistic, useTransition, ViewTransition } from 'react'; + +export function Tabs({ tabs, active, indicatorName = 'tab-indicator' }) { + const [optimisticActive, setOptimisticActive] = useOptimistic(active); + const [, startTransition] = useTransition(); + return ( + + ); +} +``` --- @@ -149,28 +209,34 @@ Same rules as explicit ``: use simple string props (not type maps) sin ``` +If the pair's `share` is type-keyed (or classed via CSS that expects a type), every `` between the two views must carry the type via `transitionTypes` — a plain link click resolves the share map's `default`, and if that's `none` the morph silently never fires. + --- ## Same-Route Dynamic Segment Transitions -When navigating between dynamic segments of the same route (e.g., `/collection/[slug]`), the page stays mounted — enter/exit never fire. Use `key` + `name` + `share`: +When navigating between dynamic segments of the same route (e.g., `/collection/[slug]`), the router swaps subtrees keyed by the segment value rather than doing a plain unmount/mount — enter/exit don't fire reliably. Use `key` + `name` + `share`: ```tsx }> - + ``` - `key={slug}` forces unmount/remount on change -- `name` + `share="auto"` creates a shared element crossfade +- The stable `name` pairs the outgoing and incoming containers; `share="auto"` creates the crossfade - VT inside `` (without keying Suspense) keeps old content visible during loading --- +## Nested enter/exit — `parentEnter` / `parentExit` (experimental) + +Lifts the "nested VTs don't fire enter/exit inside a parent" rule: a nested VT can animate when its **parent** enters/exits (`parentEnter`/`parentExit`, `onParentEnter`/`onParentExit`; `parentEnter="none"` stops propagation). Experimental-channel only today; SSR support landed in React PR #36917 ([commit](https://github.com/facebook/react/commit/83840902c890f0eb85decda239ef6b1b14945779)). Verify it's in the React your app runs: `grep -c "parentEnter" node_modules/next/dist/compiled/react-dom/cjs/react-dom-client.production.js` — 0 means unavailable (Next uses the experimental channel only when `gestureTransition`/`blockingSSR`/`taint`/`transitionIndicator` is set). + ## Server Components - `` works in both Server and Client Components - `` works in Server Components — no `'use client'` needed -- `addTransitionType` and `startTransition` for programmatic nav require Client Components +- `router.push(..., { transitionTypes })`, `addTransitionType`, and `startTransition` require Client Components diff --git a/plugins/react/.agents/skills/vercel-react-view-transitions/references/patterns.md b/plugins/react/.agents/skills/vercel-react-view-transitions/references/patterns.md index 6919ea4a..23bc102a 100644 --- a/plugins/react/.agents/skills/vercel-react-view-transitions/references/patterns.md +++ b/plugins/react/.agents/skills/vercel-react-view-transitions/references/patterns.md @@ -114,38 +114,74 @@ Use `key` when content identity changes (state resets). Omit for cross-fades (ta ## Isolate Elements from Parent Animations -### Persistent Layout Elements +Pull an element out of the animated `root` snapshot by giving it its own `view-transition-name`. **`view-transition-name: none` is a no-op** — it's the CSS default, so the element stays in `root` (a common flicker bug). Use a real, unique name, then neutralize with `` (no CSS) or CSS (needed for `z-index`/`display` control — see [css-recipes.md](css-recipes.md#persistent-element-isolation)). -Persistent elements (headers, navbars, sidebars) get captured in the page's transition snapshot. Fix with `viewTransitionName`: +- **Persistent chrome** (nav, sidebar, player bar): `