{"owner":"vercel-labs","repo":"open-agents","hasSkills":true,"totalSkillsCount":90,"totalTokensCount":79502,"categories":["anthropic-skill","root-instruction","claude-rule","subagent-persona"],"hasMcp":false,"mcpConfig":null,"found":[".agents/skills/agent-browser/SKILL.md",".agents/skills/agent-browser/references/authentication.md",".agents/skills/agent-browser/references/proxy-support.md",".agents/skills/agent-browser/references/session-management.md",".agents/skills/agent-browser/references/snapshot-refs.md",".agents/skills/agent-browser/references/video-recording.md",".agents/skills/ai-sdk/SKILL.md",".agents/skills/ai-sdk/references/ai-gateway.md",".agents/skills/ai-sdk/references/common-errors.md",".agents/skills/ai-sdk/references/devtools.md",".agents/skills/ai-sdk/references/type-safe-agents.md",".agents/skills/baseline-ui/SKILL.md",".agents/skills/chat-sdk/SKILL.md",".agents/skills/code-review/SKILL.md",".agents/skills/deploy-open-harness/SKILL.md",".agents/skills/emil-design-eng/SKILL.md",".agents/skills/frontend-design/SKILL.md",".agents/skills/plan-mode/SKILL.md",".agents/skills/remove-demo-limits/SKILL.md",".agents/skills/vercel-react-best-practices/AGENTS.md",".agents/skills/vercel-react-best-practices/README.md",".agents/skills/vercel-react-best-practices/SKILL.md",".agents/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md",".agents/skills/vercel-react-best-practices/rules/advanced-init-once.md",".agents/skills/vercel-react-best-practices/rules/advanced-use-latest.md",".agents/skills/vercel-react-best-practices/rules/async-api-routes.md",".agents/skills/vercel-react-best-practices/rules/async-defer-await.md",".agents/skills/vercel-react-best-practices/rules/async-dependencies.md",".agents/skills/vercel-react-best-practices/rules/async-parallel.md",".agents/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md",".agents/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md",".agents/skills/vercel-react-best-practices/rules/bundle-conditional.md",".agents/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md",".agents/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md",".agents/skills/vercel-react-best-practices/rules/bundle-preload.md",".agents/skills/vercel-react-best-practices/rules/client-event-listeners.md",".agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md",".agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md",".agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md",".agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md",".agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md",".agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md",".agents/skills/vercel-react-best-practices/rules/js-cache-storage.md",".agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md",".agents/skills/vercel-react-best-practices/rules/js-early-exit.md",".agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md",".agents/skills/vercel-react-best-practices/rules/js-index-maps.md",".agents/skills/vercel-react-best-practices/rules/js-length-check-first.md",".agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md",".agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md",".agents/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md",".agents/skills/vercel-react-best-practices/rules/rendering-activity.md",".agents/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md",".agents/skills/vercel-react-best-practices/rules/rendering-conditional-render.md",".agents/skills/vercel-react-best-practices/rules/rendering-content-visibility.md",".agents/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md",".agents/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md",".agents/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md",".agents/skills/vercel-react-best-practices/rules/rendering-svg-precision.md",".agents/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md",".agents/skills/vercel-react-best-practices/rules/rerender-defer-reads.md",".agents/skills/vercel-react-best-practices/rules/rerender-dependencies.md",".agents/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md",".agents/skills/vercel-react-best-practices/rules/rerender-derived-state.md",".agents/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md",".agents/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md",".agents/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md",".agents/skills/vercel-react-best-practices/rules/rerender-memo.md",".agents/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md",".agents/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md",".agents/skills/vercel-react-best-practices/rules/rerender-transitions.md",".agents/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md",".agents/skills/vercel-react-best-practices/rules/server-after-nonblocking.md",".agents/skills/vercel-react-best-practices/rules/server-auth-actions.md",".agents/skills/vercel-react-best-practices/rules/server-cache-lru.md",".agents/skills/vercel-react-best-practices/rules/server-cache-react.md",".agents/skills/vercel-react-best-practices/rules/server-dedup-props.md",".agents/skills/vercel-react-best-practices/rules/server-hoist-static-io.md",".agents/skills/vercel-react-best-practices/rules/server-parallel-fetching.md",".agents/skills/vercel-react-best-practices/rules/server-serialization.md",".agents/skills/web-animation-design/PRACTICAL-TIPS.md",".agents/skills/web-animation-design/SKILL.md",".agents/skills/workflow/SKILL.md","AGENTS.md","CLAUDE.md","docs/agents/architecture.md","docs/agents/code-style.md","docs/agents/lessons-learned.md","docs/agents/react-best-practices-audit.md","packages/agent/docs/approval-system.md"],"skills":{".agents/skills/agent-browser/SKILL.md":"---\nname: agent-browser\ndescription: Automates browser interactions for web testing, form filling, screenshots, and data extraction. Use when the user needs to navigate websites, interact with web pages, fill forms, take screenshots, test web applications, or extract information from web pages.\nallowed-tools: Bash(agent-browser:*)\n---\n\n# Browser Automation with agent-browser\n\n## Quick start\n\n```bash\nagent-browser open <url>        # Navigate to page\nagent-browser snapshot -i       # Get interactive elements with refs\nagent-browser click @e1         # Click element by ref\nagent-browser fill @e2 \"text\"   # Fill input by ref\nagent-browser close             # Close browser\n```\n\n## Core workflow\n\n1. Navigate: `agent-browser open <url>`\n2. Snapshot: `agent-browser snapshot -i` (returns elements with refs like `@e1`, `@e2`)\n3. Interact using refs from the snapshot\n4. Re-snapshot after navigation or significant DOM changes\n\n## Commands\n\n### Navigation\n\n```bash\nagent-browser open <url>      # Navigate to URL (aliases: goto, navigate)\n                              # Supports: https://, http://, file://, about:, data://\n                              # Auto-prepends https:// if no protocol given\nagent-browser back            # Go back\nagent-browser forward         # Go forward\nagent-browser reload          # Reload page\nagent-browser close           # Close browser (aliases: quit, exit)\nagent-browser connect 9222    # Connect to browser via CDP port\n```\n\n### Snapshot (page analysis)\n\n```bash\nagent-browser snapshot            # Full accessibility tree\nagent-browser snapshot -i         # Interactive elements only (recommended)\nagent-browser snapshot -c         # Compact output\nagent-browser snapshot -d 3       # Limit depth to 3\nagent-browser snapshot -s \"#main\" # Scope to CSS selector\n```\n\n### Interactions (use @refs from snapshot)\n\n```bash\nagent-browser click @e1           # Click\nagent-browser dblclick @e1        # Double-click\nagent-browser focus @e1           # Focus element\nagent-browser fill @e2 \"text\"     # Clear and type\nagent-browser type @e2 \"text\"     # Type without clearing\nagent-browser press Enter         # Press key (alias: key)\nagent-browser press Control+a     # Key combination\nagent-browser keydown Shift       # Hold key down\nagent-browser keyup Shift         # Release key\nagent-browser hover @e1           # Hover\nagent-browser check @e1           # Check checkbox\nagent-browser uncheck @e1         # Uncheck checkbox\nagent-browser select @e1 \"value\"  # Select dropdown option\nagent-browser select @e1 \"a\" \"b\"  # Select multiple options\nagent-browser scroll down 500     # Scroll page (default: down 300px)\nagent-browser scrollintoview @e1  # Scroll element into view (alias: scrollinto)\nagent-browser drag @e1 @e2        # Drag and drop\nagent-browser upload @e1 file.pdf # Upload files\n```\n\n### Get information\n\n```bash\nagent-browser get text @e1        # Get element text\nagent-browser get html @e1        # Get innerHTML\nagent-browser get value @e1       # Get input value\nagent-browser get attr @e1 href   # Get attribute\nagent-browser get title           # Get page title\nagent-browser get url             # Get current URL\nagent-browser get count \".item\"   # Count matching elements\nagent-browser get box @e1         # Get bounding box\nagent-browser get styles @e1      # Get computed styles (font, color, bg, etc.)\n```\n\n### Check state\n\n```bash\nagent-browser is visible @e1      # Check if visible\nagent-browser is enabled @e1      # Check if enabled\nagent-browser is checked @e1      # Check if checked\n```\n\n### Screenshots & PDF\n\n```bash\nagent-browser screenshot          # Save to a temporary directory\nagent-browser screenshot path.png # Save to a specific path\nagent-browser screenshot --full   # Full page\nagent-browser pdf output.pdf      # Save as PDF\n```\n\n### Video recording\n\n```bash\nagent-browser record start ./demo.webm    # Start recording (uses current URL + state)\nagent-browser click @e1                   # Perform actions\nagent-browser record stop                 # Stop and save video\nagent-browser record restart ./take2.webm # Stop current + start new recording\n```\n\nRecording creates a fresh context but preserves cookies/storage from your session. If no URL is provided, it\nautomatically returns to your current page. For smooth demos, explore first, then start recording.\n\n### Wait\n\n```bash\nagent-browser wait @e1                     # Wait for element\nagent-browser wait 2000                    # Wait milliseconds\nagent-browser wait --text \"Success\"        # Wait for text (or -t)\nagent-browser wait --url \"**/dashboard\"    # Wait for URL pattern (or -u)\nagent-browser wait --load networkidle      # Wait for network idle (or -l)\nagent-browser wait --fn \"window.ready\"     # Wait for JS condition (or -f)\n```\n\n### Mouse control\n\n```bash\nagent-browser mouse move 100 200      # Move mouse\nagent-browser mouse down left         # Press button\nagent-browser mouse up left           # Release button\nagent-browser mouse wheel 100         # Scroll wheel\n```\n\n### Semantic locators (alternative to refs)\n\n```bash\nagent-browser find role button click --name \"Submit\"\nagent-browser find text \"Sign In\" click\nagent-browser find text \"Sign In\" click --exact      # Exact match only\nagent-browser find label \"Email\" fill \"user@test.com\"\nagent-browser find placeholder \"Search\" type \"query\"\nagent-browser find alt \"Logo\" click\nagent-browser find title \"Close\" click\nagent-browser find testid \"submit-btn\" click\nagent-browser find first \".item\" click\nagent-browser find last \".item\" click\nagent-browser find nth 2 \"a\" hover\n```\n\n### Browser settings\n\n```bash\nagent-browser set viewport 1920 1080          # Set viewport size\nagent-browser set device \"iPhone 14\"          # Emulate device\nagent-browser set geo 37.7749 -122.4194       # Set geolocation (alias: geolocation)\nagent-browser set offline on                  # Toggle offline mode\nagent-browser set headers '{\"X-Key\":\"v\"}'     # Extra HTTP headers\nagent-browser set credentials user pass       # HTTP basic auth (alias: auth)\nagent-browser set media dark                  # Emulate color scheme\nagent-browser set media light reduced-motion  # Light mode + reduced motion\n```\n\n### Cookies & Storage\n\n```bash\nagent-browser cookies                     # Get all cookies\nagent-browser cookies set name value      # Set cookie\nagent-browser cookies clear               # Clear cookies\nagent-browser storage local               # Get all localStorage\nagent-browser storage local key           # Get specific key\nagent-browser storage local set k v       # Set value\nagent-browser storage local clear         # Clear all\n```\n\n### Network\n\n```bash\nagent-browser network route <url>              # Intercept requests\nagent-browser network route <url> --abort      # Block requests\nagent-browser network route <url> --body '{}'  # Mock response\nagent-browser network unroute [url]            # Remove routes\nagent-browser network requests                 # View tracked requests\nagent-browser network requests --filter api    # Filter requests\n```\n\n### Tabs & Windows\n\n```bash\nagent-browser tab                 # List tabs\nagent-browser tab new [url]       # New tab\nagent-browser tab 2               # Switch to tab by index\nagent-browser tab close           # Close current tab\nagent-browser tab close 2         # Close tab by index\nagent-browser window new          # New window\n```\n\n### Frames\n\n```bash\nagent-browser frame \"#iframe\"     # Switch to iframe\nagent-browser frame main          # Back to main frame\n```\n\n### Dialogs\n\n```bash\nagent-browser dialog accept [text]  # Accept dialog\nagent-browser dialog dismiss        # Dismiss dialog\n```\n\n### JavaScript\n\n```bash\nagent-browser eval \"document.title\"   # Run JavaScript\n```\n\n## Global options\n\n```bash\nagent-browser --session <name> ...    # Isolated browser session\nagent-browser --json ...              # JSON output for parsing\nagent-browser --headed ...            # Show browser window (not headless)\nagent-browser --full ...              # Full page screenshot (-f)\nagent-browser --cdp <port> ...        # Connect via Chrome DevTools Protocol\nagent-browser -p <provider> ...       # Cloud browser provider (--provider)\nagent-browser --proxy <url> ...       # Use proxy server\nagent-browser --headers <json> ...    # HTTP headers scoped to URL's origin\nagent-browser --executable-path <p>   # Custom browser executable\nagent-browser --extension <path> ...  # Load browser extension (repeatable)\nagent-browser --help                  # Show help (-h)\nagent-browser --version               # Show version (-V)\nagent-browser <command> --help        # Show detailed help for a command\n```\n\n### Proxy support\n\n```bash\nagent-browser --proxy http://proxy.com:8080 open example.com\nagent-browser --proxy http://user:pass@proxy.com:8080 open example.com\nagent-browser --proxy socks5://proxy.com:1080 open example.com\n```\n\n## Environment variables\n\n```bash\nAGENT_BROWSER_SESSION=\"mysession\"            # Default session name\nAGENT_BROWSER_EXECUTABLE_PATH=\"/path/chrome\" # Custom browser path\nAGENT_BROWSER_EXTENSIONS=\"/ext1,/ext2\"       # Comma-separated extension paths\nAGENT_BROWSER_PROVIDER=\"your-cloud-browser-provider\"  # Cloud browser provider (select browseruse or browserbase)\nAGENT_BROWSER_STREAM_PORT=\"9223\"             # WebSocket streaming port\nAGENT_BROWSER_HOME=\"/path/to/agent-browser\"  # Custom install location (for daemon.js)\n```\n\n## Example: Form submission\n\n```bash\nagent-browser open https://example.com/form\nagent-browser snapshot -i\n# Output shows: textbox \"Email\" [ref=e1], textbox \"Password\" [ref=e2], button \"Submit\" [ref=e3]\n\nagent-browser fill @e1 \"user@example.com\"\nagent-browser fill @e2 \"password123\"\nagent-browser click @e3\nagent-browser wait --load networkidle\nagent-browser snapshot -i  # Check result\n```\n\n## Example: Authentication with saved state\n\n```bash\n# Login once\nagent-browser open https://app.example.com/login\nagent-browser snapshot -i\nagent-browser fill @e1 \"username\"\nagent-browser fill @e2 \"password\"\nagent-browser click @e3\nagent-browser wait --url \"**/dashboard\"\nagent-browser state save auth.json\n\n# Later sessions: load saved state\nagent-browser state load auth.json\nagent-browser open https://app.example.com/dashboard\n```\n\n## Sessions (parallel browsers)\n\n```bash\nagent-browser --session test1 open site-a.com\nagent-browser --session test2 open site-b.com\nagent-browser session list\n```\n\n## JSON output (for parsing)\n\nAdd `--json` for machine-readable output:\n\n```bash\nagent-browser snapshot -i --json\nagent-browser get text @e1 --json\n```\n\n## Debugging\n\n```bash\nagent-browser --headed open example.com   # Show browser window\nagent-browser --cdp 9222 snapshot         # Connect via CDP port\nagent-browser connect 9222                # Alternative: connect command\nagent-browser console                     # View console messages\nagent-browser console --clear             # Clear console\nagent-browser errors                      # View page errors\nagent-browser errors --clear              # Clear errors\nagent-browser highlight @e1               # Highlight element\nagent-browser trace start                 # Start recording trace\nagent-browser trace stop trace.zip        # Stop and save trace\nagent-browser record start ./debug.webm   # Record video from current page\nagent-browser record stop                 # Save recording\n```\n\n## Deep-dive documentation\n\nFor detailed patterns and best practices, see:\n\n| Reference | Description |\n|-----------|-------------|\n| [references/snapshot-refs.md](references/snapshot-refs.md) | Ref lifecycle, invalidation rules, troubleshooting |\n| [references/session-management.md](references/session-management.md) | Parallel sessions, state persistence, concurrent scraping |\n| [references/authentication.md](references/authentication.md) | Login flows, OAuth, 2FA handling, state reuse |\n| [references/video-recording.md](references/video-recording.md) | Recording workflows for debugging and documentation |\n| [references/proxy-support.md](references/proxy-support.md) | Proxy configuration, geo-testing, rotating proxies |\n\n## Ready-to-use templates\n\nExecutable workflow scripts for common patterns:\n\n| Template | Description |\n|----------|-------------|\n| [templates/form-automation.sh](templates/form-automation.sh) | Form filling with validation |\n| [templates/authenticated-session.sh](templates/authenticated-session.sh) | Login once, reuse state |\n| [templates/capture-workflow.sh](templates/capture-workflow.sh) | Content extraction with screenshots |\n\nUsage:\n```bash\n./templates/form-automation.sh https://example.com/form\n./templates/authenticated-session.sh https://app.example.com/login\n./templates/capture-workflow.sh https://example.com ./output\n```\n\n## HTTPS Certificate Errors\n\nFor sites with self-signed or invalid certificates:\n```bash\nagent-browser open https://localhost:8443 --ignore-https-errors\n```\n",".agents/skills/agent-browser/references/authentication.md":"# Authentication Patterns\n\nPatterns for handling login flows, session persistence, and authenticated browsing.\n\n## Basic Login Flow\n\n```bash\n# Navigate to login page\nagent-browser open https://app.example.com/login\nagent-browser wait --load networkidle\n\n# Get form elements\nagent-browser snapshot -i\n# Output: @e1 [input type=\"email\"], @e2 [input type=\"password\"], @e3 [button] \"Sign In\"\n\n# Fill credentials\nagent-browser fill @e1 \"user@example.com\"\nagent-browser fill @e2 \"password123\"\n\n# Submit\nagent-browser click @e3\nagent-browser wait --load networkidle\n\n# Verify login succeeded\nagent-browser get url  # Should be dashboard, not login\n```\n\n## Saving Authentication State\n\nAfter logging in, save state for reuse:\n\n```bash\n# Login first (see above)\nagent-browser open https://app.example.com/login\nagent-browser snapshot -i\nagent-browser fill @e1 \"user@example.com\"\nagent-browser fill @e2 \"password123\"\nagent-browser click @e3\nagent-browser wait --url \"**/dashboard\"\n\n# Save authenticated state\nagent-browser state save ./auth-state.json\n```\n\n## Restoring Authentication\n\nSkip login by loading saved state:\n\n```bash\n# Load saved auth state\nagent-browser state load ./auth-state.json\n\n# Navigate directly to protected page\nagent-browser open https://app.example.com/dashboard\n\n# Verify authenticated\nagent-browser snapshot -i\n```\n\n## OAuth / SSO Flows\n\nFor OAuth redirects:\n\n```bash\n# Start OAuth flow\nagent-browser open https://app.example.com/auth/google\n\n# Handle redirects automatically\nagent-browser wait --url \"**/accounts.google.com**\"\nagent-browser snapshot -i\n\n# Fill Google credentials\nagent-browser fill @e1 \"user@gmail.com\"\nagent-browser click @e2  # Next button\nagent-browser wait 2000\nagent-browser snapshot -i\nagent-browser fill @e3 \"password\"\nagent-browser click @e4  # Sign in\n\n# Wait for redirect back\nagent-browser wait --url \"**/app.example.com**\"\nagent-browser state save ./oauth-state.json\n```\n\n## Two-Factor Authentication\n\nHandle 2FA with manual intervention:\n\n```bash\n# Login with credentials\nagent-browser open https://app.example.com/login --headed  # Show browser\nagent-browser snapshot -i\nagent-browser fill @e1 \"user@example.com\"\nagent-browser fill @e2 \"password123\"\nagent-browser click @e3\n\n# Wait for user to complete 2FA manually\necho \"Complete 2FA in the browser window...\"\nagent-browser wait --url \"**/dashboard\" --timeout 120000\n\n# Save state after 2FA\nagent-browser state save ./2fa-state.json\n```\n\n## HTTP Basic Auth\n\nFor sites using HTTP Basic Authentication:\n\n```bash\n# Set credentials before navigation\nagent-browser set credentials username password\n\n# Navigate to protected resource\nagent-browser open https://protected.example.com/api\n```\n\n## Cookie-Based Auth\n\nManually set authentication cookies:\n\n```bash\n# Set auth cookie\nagent-browser cookies set session_token \"abc123xyz\"\n\n# Navigate to protected page\nagent-browser open https://app.example.com/dashboard\n```\n\n## Token Refresh Handling\n\nFor sessions with expiring tokens:\n\n```bash\n#!/bin/bash\n# Wrapper that handles token refresh\n\nSTATE_FILE=\"./auth-state.json\"\n\n# Try loading existing state\nif [[ -f \"$STATE_FILE\" ]]; then\n    agent-browser state load \"$STATE_FILE\"\n    agent-browser open https://app.example.com/dashboard\n\n    # Check if session is still valid\n    URL=$(agent-browser get url)\n    if [[ \"$URL\" == *\"/login\"* ]]; then\n        echo \"Session expired, re-authenticating...\"\n        # Perform fresh login\n        agent-browser snapshot -i\n        agent-browser fill @e1 \"$USERNAME\"\n        agent-browser fill @e2 \"$PASSWORD\"\n        agent-browser click @e3\n        agent-browser wait --url \"**/dashboard\"\n        agent-browser state save \"$STATE_FILE\"\n    fi\nelse\n    # First-time login\n    agent-browser open https://app.example.com/login\n    # ... login flow ...\nfi\n```\n\n## Security Best Practices\n\n1. **Never commit state files** - They contain session tokens\n   ```bash\n   echo \"*.auth-state.json\" >> .gitignore\n   ```\n\n2. **Use environment variables for credentials**\n   ```bash\n   agent-browser fill @e1 \"$APP_USERNAME\"\n   agent-browser fill @e2 \"$APP_PASSWORD\"\n   ```\n\n3. **Clean up after automation**\n   ```bash\n   agent-browser cookies clear\n   rm -f ./auth-state.json\n   ```\n\n4. **Use short-lived sessions for CI/CD**\n   ```bash\n   # Don't persist state in CI\n   agent-browser open https://app.example.com/login\n   # ... login and perform actions ...\n   agent-browser close  # Session ends, nothing persisted\n   ```\n",".agents/skills/agent-browser/references/proxy-support.md":"# Proxy Support\n\nConfigure proxy servers for browser automation, useful for geo-testing, rate limiting avoidance, and corporate environments.\n\n## Basic Proxy Configuration\n\nSet proxy via environment variable before starting:\n\n```bash\n# HTTP proxy\nexport HTTP_PROXY=\"http://proxy.example.com:8080\"\nagent-browser open https://example.com\n\n# HTTPS proxy\nexport HTTPS_PROXY=\"https://proxy.example.com:8080\"\nagent-browser open https://example.com\n\n# Both\nexport HTTP_PROXY=\"http://proxy.example.com:8080\"\nexport HTTPS_PROXY=\"http://proxy.example.com:8080\"\nagent-browser open https://example.com\n```\n\n## Authenticated Proxy\n\nFor proxies requiring authentication:\n\n```bash\n# Include credentials in URL\nexport HTTP_PROXY=\"http://username:password@proxy.example.com:8080\"\nagent-browser open https://example.com\n```\n\n## SOCKS Proxy\n\n```bash\n# SOCKS5 proxy\nexport ALL_PROXY=\"socks5://proxy.example.com:1080\"\nagent-browser open https://example.com\n\n# SOCKS5 with auth\nexport ALL_PROXY=\"socks5://user:pass@proxy.example.com:1080\"\nagent-browser open https://example.com\n```\n\n## Proxy Bypass\n\nSkip proxy for specific domains:\n\n```bash\n# Bypass proxy for local addresses\nexport NO_PROXY=\"localhost,127.0.0.1,.internal.company.com\"\nagent-browser open https://internal.company.com  # Direct connection\nagent-browser open https://external.com          # Via proxy\n```\n\n## Common Use Cases\n\n### Geo-Location Testing\n\n```bash\n#!/bin/bash\n# Test site from different regions using geo-located proxies\n\nPROXIES=(\n    \"http://us-proxy.example.com:8080\"\n    \"http://eu-proxy.example.com:8080\"\n    \"http://asia-proxy.example.com:8080\"\n)\n\nfor proxy in \"${PROXIES[@]}\"; do\n    export HTTP_PROXY=\"$proxy\"\n    export HTTPS_PROXY=\"$proxy\"\n\n    region=$(echo \"$proxy\" | grep -oP '^\\w+-\\w+')\n    echo \"Testing from: $region\"\n\n    agent-browser --session \"$region\" open https://example.com\n    agent-browser --session \"$region\" screenshot \"./screenshots/$region.png\"\n    agent-browser --session \"$region\" close\ndone\n```\n\n### Rotating Proxies for Scraping\n\n```bash\n#!/bin/bash\n# Rotate through proxy list to avoid rate limiting\n\nPROXY_LIST=(\n    \"http://proxy1.example.com:8080\"\n    \"http://proxy2.example.com:8080\"\n    \"http://proxy3.example.com:8080\"\n)\n\nURLS=(\n    \"https://site.com/page1\"\n    \"https://site.com/page2\"\n    \"https://site.com/page3\"\n)\n\nfor i in \"${!URLS[@]}\"; do\n    proxy_index=$((i % ${#PROXY_LIST[@]}))\n    export HTTP_PROXY=\"${PROXY_LIST[$proxy_index]}\"\n    export HTTPS_PROXY=\"${PROXY_LIST[$proxy_index]}\"\n\n    agent-browser open \"${URLS[$i]}\"\n    agent-browser get text body > \"output-$i.txt\"\n    agent-browser close\n\n    sleep 1  # Polite delay\ndone\n```\n\n### Corporate Network Access\n\n```bash\n#!/bin/bash\n# Access internal sites via corporate proxy\n\nexport HTTP_PROXY=\"http://corpproxy.company.com:8080\"\nexport HTTPS_PROXY=\"http://corpproxy.company.com:8080\"\nexport NO_PROXY=\"localhost,127.0.0.1,.company.com\"\n\n# External sites go through proxy\nagent-browser open https://external-vendor.com\n\n# Internal sites bypass proxy\nagent-browser open https://intranet.company.com\n```\n\n## Verifying Proxy Connection\n\n```bash\n# Check your apparent IP\nagent-browser open https://httpbin.org/ip\nagent-browser get text body\n# Should show proxy's IP, not your real IP\n```\n\n## Troubleshooting\n\n### Proxy Connection Failed\n\n```bash\n# Test proxy connectivity first\ncurl -x http://proxy.example.com:8080 https://httpbin.org/ip\n\n# Check if proxy requires auth\nexport HTTP_PROXY=\"http://user:pass@proxy.example.com:8080\"\n```\n\n### SSL/TLS Errors Through Proxy\n\nSome proxies perform SSL inspection. If you encounter certificate errors:\n\n```bash\n# For testing only - not recommended for production\nagent-browser open https://example.com --ignore-https-errors\n```\n\n### Slow Performance\n\n```bash\n# Use proxy only when necessary\nexport NO_PROXY=\"*.cdn.com,*.static.com\"  # Direct CDN access\n```\n\n## Best Practices\n\n1. **Use environment variables** - Don't hardcode proxy credentials\n2. **Set NO_PROXY appropriately** - Avoid routing local traffic through proxy\n3. **Test proxy before automation** - Verify connectivity with simple requests\n4. **Handle proxy failures gracefully** - Implement retry logic for unstable proxies\n5. **Rotate proxies for large scraping jobs** - Distribute load and avoid bans\n",".agents/skills/agent-browser/references/session-management.md":"# Session Management\n\nRun multiple isolated browser sessions concurrently with state persistence.\n\n## Named Sessions\n\nUse `--session` flag to isolate browser contexts:\n\n```bash\n# Session 1: Authentication flow\nagent-browser --session auth open https://app.example.com/login\n\n# Session 2: Public browsing (separate cookies, storage)\nagent-browser --session public open https://example.com\n\n# Commands are isolated by session\nagent-browser --session auth fill @e1 \"user@example.com\"\nagent-browser --session public get text body\n```\n\n## Session Isolation Properties\n\nEach session has independent:\n- Cookies\n- LocalStorage / SessionStorage\n- IndexedDB\n- Cache\n- Browsing history\n- Open tabs\n\n## Session State Persistence\n\n### Save Session State\n\n```bash\n# Save cookies, storage, and auth state\nagent-browser state save /path/to/auth-state.json\n```\n\n### Load Session State\n\n```bash\n# Restore saved state\nagent-browser state load /path/to/auth-state.json\n\n# Continue with authenticated session\nagent-browser open https://app.example.com/dashboard\n```\n\n### State File Contents\n\n```json\n{\n  \"cookies\": [...],\n  \"localStorage\": {...},\n  \"sessionStorage\": {...},\n  \"origins\": [...]\n}\n```\n\n## Common Patterns\n\n### Authenticated Session Reuse\n\n```bash\n#!/bin/bash\n# Save login state once, reuse many times\n\nSTATE_FILE=\"/tmp/auth-state.json\"\n\n# Check if we have saved state\nif [[ -f \"$STATE_FILE\" ]]; then\n    agent-browser state load \"$STATE_FILE\"\n    agent-browser open https://app.example.com/dashboard\nelse\n    # Perform login\n    agent-browser open https://app.example.com/login\n    agent-browser snapshot -i\n    agent-browser fill @e1 \"$USERNAME\"\n    agent-browser fill @e2 \"$PASSWORD\"\n    agent-browser click @e3\n    agent-browser wait --load networkidle\n\n    # Save for future use\n    agent-browser state save \"$STATE_FILE\"\nfi\n```\n\n### Concurrent Scraping\n\n```bash\n#!/bin/bash\n# Scrape multiple sites concurrently\n\n# Start all sessions\nagent-browser --session site1 open https://site1.com &\nagent-browser --session site2 open https://site2.com &\nagent-browser --session site3 open https://site3.com &\nwait\n\n# Extract from each\nagent-browser --session site1 get text body > site1.txt\nagent-browser --session site2 get text body > site2.txt\nagent-browser --session site3 get text body > site3.txt\n\n# Cleanup\nagent-browser --session site1 close\nagent-browser --session site2 close\nagent-browser --session site3 close\n```\n\n### A/B Testing Sessions\n\n```bash\n# Test different user experiences\nagent-browser --session variant-a open \"https://app.com?variant=a\"\nagent-browser --session variant-b open \"https://app.com?variant=b\"\n\n# Compare\nagent-browser --session variant-a screenshot /tmp/variant-a.png\nagent-browser --session variant-b screenshot /tmp/variant-b.png\n```\n\n## Default Session\n\nWhen `--session` is omitted, commands use the default session:\n\n```bash\n# These use the same default session\nagent-browser open https://example.com\nagent-browser snapshot -i\nagent-browser close  # Closes default session\n```\n\n## Session Cleanup\n\n```bash\n# Close specific session\nagent-browser --session auth close\n\n# List active sessions\nagent-browser session list\n```\n\n## Best Practices\n\n### 1. Name Sessions Semantically\n\n```bash\n# GOOD: Clear purpose\nagent-browser --session github-auth open https://github.com\nagent-browser --session docs-scrape open https://docs.example.com\n\n# AVOID: Generic names\nagent-browser --session s1 open https://github.com\n```\n\n### 2. Always Clean Up\n\n```bash\n# Close sessions when done\nagent-browser --session auth close\nagent-browser --session scrape close\n```\n\n### 3. Handle State Files Securely\n\n```bash\n# Don't commit state files (contain auth tokens!)\necho \"*.auth-state.json\" >> .gitignore\n\n# Delete after use\nrm /tmp/auth-state.json\n```\n\n### 4. Timeout Long Sessions\n\n```bash\n# Set timeout for automated scripts\ntimeout 60 agent-browser --session long-task get text body\n```\n",".agents/skills/agent-browser/references/snapshot-refs.md":"# Snapshot + Refs Workflow\n\nThe core innovation of agent-browser: compact element references that reduce context usage dramatically for AI agents.\n\n## How It Works\n\n### The Problem\nTraditional browser automation sends full DOM to AI agents:\n```\nFull DOM/HTML sent → AI parses → Generates CSS selector → Executes action\n~3000-5000 tokens per interaction\n```\n\n### The Solution\nagent-browser uses compact snapshots with refs:\n```\nCompact snapshot → @refs assigned → Direct ref interaction\n~200-400 tokens per interaction\n```\n\n## The Snapshot Command\n\n```bash\n# Basic snapshot (shows page structure)\nagent-browser snapshot\n\n# Interactive snapshot (-i flag) - RECOMMENDED\nagent-browser snapshot -i\n```\n\n### Snapshot Output Format\n\n```\nPage: Example Site - Home\nURL: https://example.com\n\n@e1 [header]\n  @e2 [nav]\n    @e3 [a] \"Home\"\n    @e4 [a] \"Products\"\n    @e5 [a] \"About\"\n  @e6 [button] \"Sign In\"\n\n@e7 [main]\n  @e8 [h1] \"Welcome\"\n  @e9 [form]\n    @e10 [input type=\"email\"] placeholder=\"Email\"\n    @e11 [input type=\"password\"] placeholder=\"Password\"\n    @e12 [button type=\"submit\"] \"Log In\"\n\n@e13 [footer]\n  @e14 [a] \"Privacy Policy\"\n```\n\n## Using Refs\n\nOnce you have refs, interact directly:\n\n```bash\n# Click the \"Sign In\" button\nagent-browser click @e6\n\n# Fill email input\nagent-browser fill @e10 \"user@example.com\"\n\n# Fill password\nagent-browser fill @e11 \"password123\"\n\n# Submit the form\nagent-browser click @e12\n```\n\n## Ref Lifecycle\n\n**IMPORTANT**: Refs are invalidated when the page changes!\n\n```bash\n# Get initial snapshot\nagent-browser snapshot -i\n# @e1 [button] \"Next\"\n\n# Click triggers page change\nagent-browser click @e1\n\n# MUST re-snapshot to get new refs!\nagent-browser snapshot -i\n# @e1 [h1] \"Page 2\"  ← Different element now!\n```\n\n## Best Practices\n\n### 1. Always Snapshot Before Interacting\n\n```bash\n# CORRECT\nagent-browser open https://example.com\nagent-browser snapshot -i          # Get refs first\nagent-browser click @e1            # Use ref\n\n# WRONG\nagent-browser open https://example.com\nagent-browser click @e1            # Ref doesn't exist yet!\n```\n\n### 2. Re-Snapshot After Navigation\n\n```bash\nagent-browser click @e5            # Navigates to new page\nagent-browser snapshot -i          # Get new refs\nagent-browser click @e1            # Use new refs\n```\n\n### 3. Re-Snapshot After Dynamic Changes\n\n```bash\nagent-browser click @e1            # Opens dropdown\nagent-browser snapshot -i          # See dropdown items\nagent-browser click @e7            # Select item\n```\n\n### 4. Snapshot Specific Regions\n\nFor complex pages, snapshot specific areas:\n\n```bash\n# Snapshot just the form\nagent-browser snapshot @e9\n```\n\n## Ref Notation Details\n\n```\n@e1 [tag type=\"value\"] \"text content\" placeholder=\"hint\"\n│    │   │             │               │\n│    │   │             │               └─ Additional attributes\n│    │   │             └─ Visible text\n│    │   └─ Key attributes shown\n│    └─ HTML tag name\n└─ Unique ref ID\n```\n\n### Common Patterns\n\n```\n@e1 [button] \"Submit\"                    # Button with text\n@e2 [input type=\"email\"]                 # Email input\n@e3 [input type=\"password\"]              # Password input\n@e4 [a href=\"/page\"] \"Link Text\"         # Anchor link\n@e5 [select]                             # Dropdown\n@e6 [textarea] placeholder=\"Message\"     # Text area\n@e7 [div class=\"modal\"]                  # Container (when relevant)\n@e8 [img alt=\"Logo\"]                     # Image\n@e9 [checkbox] checked                   # Checked checkbox\n@e10 [radio] selected                    # Selected radio\n```\n\n## Troubleshooting\n\n### \"Ref not found\" Error\n\n```bash\n# Ref may have changed - re-snapshot\nagent-browser snapshot -i\n```\n\n### Element Not Visible in Snapshot\n\n```bash\n# Scroll to reveal element\nagent-browser scroll --bottom\nagent-browser snapshot -i\n\n# Or wait for dynamic content\nagent-browser wait 1000\nagent-browser snapshot -i\n```\n\n### Too Many Elements\n\n```bash\n# Snapshot specific container\nagent-browser snapshot @e5\n\n# Or use get text for content-only extraction\nagent-browser get text @e5\n```\n",".agents/skills/agent-browser/references/video-recording.md":"# Recording Demos\n\nProduce polished, shareable demo videos with a visible cursor and minimal dead time.\n\nThis guide builds on the basics in `video-recording.md` with techniques specific to recording demos that look good to viewers.\n\n## Key Principles\n\n- **Minimize dead time** — chain commands with `&&` so tool-call overhead stays out of the video.\n- **Visible cursor** — headless Playwright has no cursor; inject a fake one via DOM.\n- **One chain per page** — navigation destroys the DOM cursor, so re-inject after every page change.\n- **No `sleep` calls** — tool execution provides enough natural pacing.\n\n## Step-by-step\n\n### 1. Set up the page BEFORE recording\n\nNavigate and snapshot **before** `record start`. Any time after recording starts is in the video.\n\n```bash\nagent-browser open <url>\n```\n```bash\nagent-browser snapshot -i\n```\n\nReview the snapshot to identify element refs (`@e1`, `@e2`, …) and plan the interaction sequence.\n\n### 2. Find element coordinates for mouse moves\n\nUse `getBoundingClientRect()` to get the (x, y) center of each target element. Do this **before** recording starts so the lookup doesn't appear in the video.\n\n```bash\nagent-browser eval \"(() => {\n  const el = document.querySelector('<selector>');\n  const r = el.getBoundingClientRect();\n  return JSON.stringify({ x: Math.round(r.x + r.width/2), y: Math.round(r.y + r.height/2) });\n})()\"\n```\n\n### 3. Inject a visible cursor\n\nHeadless Playwright doesn't render a cursor. Inject a DOM element that tracks mouse events:\n\n```bash\nagent-browser eval \"(() => {\n  const c = document.createElement('div');\n  c.style.cssText = 'width:20px;height:20px;border-radius:50%;background:rgba(255,50,50,0.85);position:fixed;top:290px;left:390px;z-index:999999;pointer-events:none;transition:top 0.15s ease,left 0.15s ease;box-shadow:0 0 8px rgba(255,50,50,0.5);';\n  document.body.appendChild(c);\n  document.addEventListener('mousemove', e => {\n    c.style.top=(e.clientY-10)+'px';\n    c.style.left=(e.clientX-10)+'px';\n  });\n})(); 'ok'\"\n```\n\n**Re-inject after every navigation** — page transitions destroy the DOM element.\n\n### 4. Chain all commands with `&&`\n\nEvery separate tool call adds seconds of dead time. Chain everything into as few bash calls as possible:\n\n```bash\nagent-browser record start ./demo.webm && agent-browser eval \"<inject cursor>\" && agent-browser mouse move 400 300 && agent-browser click @e1 && agent-browser mouse move 600 200 && agent-browser click @e2 && agent-browser record stop\n```\n\n### 5. Use `mouse move` before every click\n\nMove the cursor to the target first so viewers can follow the action:\n\n```bash\nagent-browser mouse move 623 52 && agent-browser click @e5\n```\n\n### 6. No `sleep` calls\n\nEvery millisecond counts. Tool execution itself provides enough natural pacing. Drop all `sleep` and `wait` calls entirely.\n\n### 7. One chain per page\n\nSince the cursor needs re-injection after navigation, structure as one `&&` chain per page. The recording persists across chains — only stop it in the final chain.\n\n```bash\n# Chain 1: first page → click navigates away\nagent-browser record start ./demo.webm && agent-browser eval \"<inject cursor>\" && agent-browser mouse move 400 300 && agent-browser click @e5\n```\n\n```bash\n# Chain 2: new page → finish\nagent-browser eval \"<inject cursor>\" && agent-browser mouse move 200 150 && agent-browser click @e3 && agent-browser scroll down 200 && agent-browser record stop\n```\n\n### 8. Serve the result\n\nCopy the `.webm` into `public/`, create a minimal viewer page, and share the sandbox URL.\n\n```bash\nmkdir -p public && cp demo.webm public/demo.webm\n```\n\nViewer page (`public/demo.html`):\n\n```html\n<!DOCTYPE html>\n<html>\n<head><title>Demo</title></head>\n<body style=\"margin:0;display:flex;justify-content:center;align-items:center;min-height:100vh;background:#111\">\n  <video controls autoplay style=\"max-width:90vw;max-height:90vh;border-radius:8px;box-shadow:0 4px 24px rgba(0,0,0,0.5)\" src=\"/demo.webm\"></video>\n</body>\n</html>\n```\n\n## Common Mistakes\n\n- **Recording before the page is ready** — navigate and snapshot first.\n- **Forgetting to re-inject the cursor after navigation** — the DOM element is gone.\n- **Separate tool calls instead of `&&` chains** — adds seconds of blank screen.\n- **Using `sleep` or `wait`** — unnecessary; tool execution provides natural pacing.\n- **Clicking without `mouse move`** — clicks without visible cursor movement look jarring.\n",".agents/skills/ai-sdk/SKILL.md":"---\nname: ai-sdk\ndescription: 'Answer questions about the AI SDK and help build AI-powered features. Use when developers: (1) Ask about AI SDK functions like generateText, streamText, ToolLoopAgent, embed, or tools, (2) Want to build AI agents, chatbots, RAG systems, or text generation features, (3) Have questions about AI providers (OpenAI, Anthropic, Google, etc.), streaming, tool calling, structured output, or embeddings, (4) Use React hooks like useChat or useCompletion. Triggers on: \"AI SDK\", \"Vercel AI SDK\", \"generateText\", \"streamText\", \"add AI to my app\", \"build an agent\", \"tool calling\", \"structured output\", \"useChat\".'\n---\n\n## Prerequisites\n\nBefore searching docs, check if `node_modules/ai/docs/` exists. If not, install **only** the `ai` package using the project's package manager (e.g., `pnpm add ai`).\n\nDo not install other packages at this stage. Provider packages (e.g., `@ai-sdk/openai`) and client packages (e.g., `@ai-sdk/react`) should be installed later when needed based on user requirements.\n\n## Critical: Do Not Trust Internal Knowledge\n\nEverything you know about the AI SDK is outdated or wrong. Your training data contains obsolete APIs, deprecated patterns, and incorrect usage.\n\n**When working with the AI SDK:**\n\n1. Ensure `ai` package is installed (see Prerequisites)\n2. Search `node_modules/ai/docs/` and `node_modules/ai/src/` for current APIs\n3. If not found locally, search ai-sdk.dev documentation (instructions below)\n4. Never rely on memory - always verify against source code or docs\n5. **`useChat` has changed significantly** - check [Common Errors](references/common-errors.md) before writing client code\n6. When deciding which model and provider to use (e.g. OpenAI, Anthropic, Gemini), use the Vercel AI Gateway provider unless the user specifies otherwise. See [AI Gateway Reference](references/ai-gateway.md) for usage details.\n7. **Always fetch current model IDs** - Never use model IDs from memory. Before writing code that uses a model, run `curl -s https://ai-gateway.vercel.sh/v1/models | jq -r '[.data[] | select(.id | startswith(\"provider/\")) | .id] | reverse | .[]'` (replacing `provider` with the relevant provider like `anthropic`, `openai`, or `google`) to get the full list with newest models first. Use the model with the highest version number (e.g., `claude-sonnet-4-5` over `claude-sonnet-4` over `claude-3-5-sonnet`).\n8. Run typecheck after changes to ensure code is correct\n9. **Be minimal** - Only specify options that differ from defaults. When unsure of defaults, check docs or source rather than guessing or over-specifying.\n\nIf you cannot find documentation to support your answer, state that explicitly.\n\n## Finding Documentation\n\n### ai@6.0.34+\n\nSearch bundled docs and source in `node_modules/ai/`:\n\n- **Docs**: `grep \"query\" node_modules/ai/docs/`\n- **Source**: `grep \"query\" node_modules/ai/src/`\n\nProvider packages include docs at `node_modules/@ai-sdk/<provider>/docs/`.\n\n### Earlier versions\n\n1. Search: `https://ai-sdk.dev/api/search-docs?q=your_query`\n2. Fetch `.md` URLs from results (e.g., `https://ai-sdk.dev/docs/agents/building-agents.md`)\n\n## When Typecheck Fails\n\n**Before searching source code**, grep [Common Errors](references/common-errors.md) for the failing property or function name. Many type errors are caused by deprecated APIs documented there.\n\nIf not found in common-errors.md:\n\n1. Search `node_modules/ai/src/` and `node_modules/ai/docs/`\n2. Search ai-sdk.dev (for earlier versions or if not found locally)\n\n## Building and Consuming Agents\n\n### Creating Agents\n\nAlways use the `ToolLoopAgent` pattern. Search `node_modules/ai/docs/` for current agent creation APIs.\n\n**File conventions**: See [type-safe-agents.md](references/type-safe-agents.md) for where to save agents and tools.\n\n**Type Safety**: When consuming agents with `useChat`, always use `InferAgentUIMessage<typeof agent>` for type-safe tool results. See [reference](references/type-safe-agents.md).\n\n### Consuming Agents (Framework-Specific)\n\nBefore implementing agent consumption:\n\n1. Check `package.json` to detect the project's framework/stack\n2. Search documentation for the framework's quickstart guide\n3. Follow the framework-specific patterns for streaming, API routes, and client integration\n\n## References\n\n- [Common Errors](references/common-errors.md) - Renamed parameters reference (parameters → inputSchema, etc.)\n- [AI Gateway](references/ai-gateway.md) - Gateway setup and usage\n- [Type-Safe Agents with useChat](references/type-safe-agents.md) - End-to-end type safety with InferAgentUIMessage\n- [DevTools](references/devtools.md) - Set up local debugging and observability (development only)\n",".agents/skills/ai-sdk/references/ai-gateway.md":"---\ntitle: Vercel AI Gateway\ndescription: Reference for using Vercel AI Gateway with the AI SDK.\n---\n\n# Vercel AI Gateway\n\nThe Vercel AI Gateway is the fastest way to get started with the AI SDK. It provides access to models from OpenAI, Anthropic, Google, and other providers through a single API.\n\n## Authentication\n\nAuthenticate with OIDC (for Vercel deployments) or an [AI Gateway API key](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai-gateway%2Fapi-keys&title=AI+Gateway+API+Keys):\n\n```env filename=\".env.local\"\nAI_GATEWAY_API_KEY=your_api_key_here\n```\n\n## Usage\n\nThe AI Gateway is the default global provider, so you can access models using a simple string:\n\n```ts\nimport { generateText } from 'ai';\n\nconst { text } = await generateText({\n  model: 'anthropic/claude-sonnet-4.5',\n  prompt: 'What is love?',\n});\n```\n\nYou can also explicitly import and use the gateway provider:\n\n```ts\n// Option 1: Import from 'ai' package (included by default)\nimport { gateway } from 'ai';\nmodel: gateway('anthropic/claude-sonnet-4.5');\n\n// Option 2: Install and import from '@ai-sdk/gateway' package\nimport { gateway } from '@ai-sdk/gateway';\nmodel: gateway('anthropic/claude-sonnet-4.5');\n```\n\n## Find Available Models\n\n**Important**: Always fetch the current model list before writing code. Never use model IDs from memory - they may be outdated.\n\nList all available models through the gateway API:\n\n```bash\ncurl https://ai-gateway.vercel.sh/v1/models\n```\n\nFilter by provider using `jq`. **Do not truncate with `head`** - always fetch the full list to find the latest models:\n\n```bash\n# Anthropic models\ncurl -s https://ai-gateway.vercel.sh/v1/models | jq -r '[.data[] | select(.id | startswith(\"anthropic/\")) | .id] | reverse | .[]'\n\n# OpenAI models\ncurl -s https://ai-gateway.vercel.sh/v1/models | jq -r '[.data[] | select(.id | startswith(\"openai/\")) | .id] | reverse | .[]'\n\n# Google models\ncurl -s https://ai-gateway.vercel.sh/v1/models | jq -r '[.data[] | select(.id | startswith(\"google/\")) | .id] | reverse | .[]'\n```\n\nWhen multiple versions of a model exist, use the one with the highest version number (e.g., prefer `claude-sonnet-4-5` over `claude-sonnet-4` over `claude-3-5-sonnet`).\n",".agents/skills/ai-sdk/references/common-errors.md":"---\ntitle: Common Errors\ndescription: Reference for common AI SDK errors and how to resolve them.\n---\n\n# Common Errors\n\n## `maxTokens` → `maxOutputTokens`\n\n```typescript\n// ❌ Incorrect\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  maxTokens: 512, // deprecated: use `maxOutputTokens` instead\n  prompt: 'Write a short story',\n});\n\n// ✅ Correct\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  maxOutputTokens: 512,\n  prompt: 'Write a short story',\n});\n```\n\n## `maxSteps` → `stopWhen: stepCountIs(n)`\n\n```typescript\n// ❌ Incorrect\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  tools: { weather },\n  maxSteps: 5, // deprecated: use `stopWhen: stepCountIs(n)` instead\n  prompt: 'What is the weather in NYC?',\n});\n\n// ✅ Correct\nimport { generateText, stepCountIs } from 'ai';\n\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  tools: { weather },\n  stopWhen: stepCountIs(5),\n  prompt: 'What is the weather in NYC?',\n});\n```\n\n## `parameters` → `inputSchema` (in tool definition)\n\n```typescript\n// ❌ Incorrect\nconst weatherTool = tool({\n  description: 'Get weather for a location',\n  parameters: z.object({\n    // deprecated: use `inputSchema` instead\n    location: z.string(),\n  }),\n  execute: async ({ location }) => ({ location, temp: 72 }),\n});\n\n// ✅ Correct\nconst weatherTool = tool({\n  description: 'Get weather for a location',\n  inputSchema: z.object({\n    location: z.string(),\n  }),\n  execute: async ({ location }) => ({ location, temp: 72 }),\n});\n```\n\n## `generateObject` → `generateText` with `output`\n\n`generateObject` is deprecated. Use `generateText` with the `output` option instead.\n\n```typescript\n// ❌ Deprecated\nimport { generateObject } from 'ai'; // deprecated: use `generateText` with `output` instead\n\nconst result = await generateObject({\n  // deprecated function\n  model: 'anthropic/claude-opus-4.5',\n  schema: z.object({\n    // deprecated: use `Output.object({ schema })` instead\n    recipe: z.object({\n      name: z.string(),\n      ingredients: z.array(z.string()),\n    }),\n  }),\n  prompt: 'Generate a recipe for chocolate cake',\n});\n\n// ✅ Correct\nimport { generateText, Output } from 'ai';\n\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  output: Output.object({\n    schema: z.object({\n      recipe: z.object({\n        name: z.string(),\n        ingredients: z.array(z.string()),\n      }),\n    }),\n  }),\n  prompt: 'Generate a recipe for chocolate cake',\n});\n\nconsole.log(result.output); // typed object\n```\n\n## Manual JSON parsing → `generateText` with `output`\n\n```typescript\n// ❌ Incorrect\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  prompt: `Extract the user info as JSON: { \"name\": string, \"age\": number }\n\n  Input: John is 25 years old`,\n});\nconst parsed = JSON.parse(result.text);\n\n// ✅ Correct\nimport { generateText, Output } from 'ai';\n\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  output: Output.object({\n    schema: z.object({\n      name: z.string(),\n      age: z.number(),\n    }),\n  }),\n  prompt: 'Extract the user info: John is 25 years old',\n});\n\nconsole.log(result.output); // { name: 'John', age: 25 }\n```\n\n## Other `output` options\n\n```typescript\n// Output.array - for generating arrays of items\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  output: Output.array({\n    element: z.object({\n      city: z.string(),\n      country: z.string(),\n    }),\n  }),\n  prompt: 'List 5 capital cities',\n});\n\n// Output.choice - for selecting from predefined options\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  output: Output.choice({\n    options: ['positive', 'negative', 'neutral'] as const,\n  }),\n  prompt: 'Classify the sentiment: I love this product!',\n});\n\n// Output.json - for untyped JSON output\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  output: Output.json(),\n  prompt: 'Return some JSON data',\n});\n```\n\n## `toDataStreamResponse` → `toUIMessageStreamResponse`\n\nWhen using `useChat` on the frontend, use `toUIMessageStreamResponse()` instead of `toDataStreamResponse()`. The UI message stream format is designed to work with the chat UI components and handles message state correctly.\n\n```typescript\n// ❌ Incorrect (when using useChat)\nconst result = streamText({\n  // config\n});\n\nreturn result.toDataStreamResponse(); // deprecated for useChat: use toUIMessageStreamResponse\n\n// ✅ Correct\nconst result = streamText({\n  // config\n});\n\nreturn result.toUIMessageStreamResponse();\n```\n\n## Removed managed input state in `useChat`\n\nThe `useChat` hook no longer manages input state internally. You must now manage input state manually.\n\n```tsx\n// ❌ Deprecated\nimport { useChat } from '@ai-sdk/react';\n\nexport default function Page() {\n  const {\n    input, // deprecated: manage input state manually with useState\n    handleInputChange, // deprecated: use custom onChange handler\n    handleSubmit, // deprecated: use sendMessage() instead\n  } = useChat({\n    api: '/api/chat', // deprecated: use `transport: new DefaultChatTransport({ api })` instead\n  });\n\n  return (\n    <form onSubmit={handleSubmit}>\n      <input value={input} onChange={handleInputChange} />\n      <button type=\"submit\">Send</button>\n    </form>\n  );\n}\n\n// ✅ Correct\nimport { useChat } from '@ai-sdk/react';\nimport { DefaultChatTransport } from 'ai';\nimport { useState } from 'react';\n\nexport default function Page() {\n  const [input, setInput] = useState('');\n  const { sendMessage } = useChat({\n    transport: new DefaultChatTransport({ api: '/api/chat' }),\n  });\n\n  const handleSubmit = e => {\n    e.preventDefault();\n    sendMessage({ text: input });\n    setInput('');\n  };\n\n  return (\n    <form onSubmit={handleSubmit}>\n      <input value={input} onChange={e => setInput(e.target.value)} />\n      <button type=\"submit\">Send</button>\n    </form>\n  );\n}\n```\n\n## `tool-invocation` → `tool-{toolName}` (typed tool parts)\n\nWhen rendering messages with `useChat`, use the typed tool part names (`tool-{toolName}`) instead of the generic `tool-invocation` type. This provides better type safety and access to tool-specific input/output types.\n\n> For end-to-end type-safety, see [Type-Safe Agents](type-safe-agents.md).\n\nTyped tool parts also use different property names:\n\n- `part.args` → `part.input`\n- `part.result` → `part.output`\n\n```tsx\n// ❌ Incorrect - using generic tool-invocation\n{\n  message.parts.map((part, i) => {\n    switch (part.type) {\n      case 'text':\n        return <div key={`${message.id}-${i}`}>{part.text}</div>;\n      case 'tool-invocation': // deprecated: use typed tool parts instead\n        return (\n          <pre key={`${message.id}-${i}`}>\n            {JSON.stringify(part.toolInvocation, null, 2)}\n          </pre>\n        );\n    }\n  });\n}\n\n// ✅ Correct - using typed tool parts (recommended)\n{\n  message.parts.map(part => {\n    switch (part.type) {\n      case 'text':\n        return part.text;\n      case 'tool-askForConfirmation':\n        // handle askForConfirmation tool\n        break;\n      case 'tool-getWeatherInformation':\n        // handle getWeatherInformation tool\n        break;\n    }\n  });\n}\n\n// ✅ Alternative - using isToolUIPart as a catch-all\nimport { isToolUIPart } from 'ai';\n\n{\n  message.parts.map(part => {\n    if (part.type === 'text') {\n      return part.text;\n    }\n    if (isToolUIPart(part)) {\n      // handle any tool part generically\n      return (\n        <div key={part.toolCallId}>\n          {part.toolName}: {part.state}\n        </div>\n      );\n    }\n  });\n}\n```\n\n## `useChat` state-dependent property access\n\nTool part properties are only available in certain states. TypeScript will error if you access them without checking state first.\n\n```tsx\n// ❌ Incorrect - input may be undefined during streaming\n// TS18048: 'part.input' is possibly 'undefined'\nif (part.type === 'tool-getWeather') {\n  const location = part.input.location;\n}\n\n// ✅ Correct - check for input-available or output-available\nif (\n  part.type === 'tool-getWeather' &&\n  (part.state === 'input-available' || part.state === 'output-available')\n) {\n  const location = part.input.location;\n}\n\n// ❌ Incorrect - output is only available after execution\n// TS18048: 'part.output' is possibly 'undefined'\nif (part.type === 'tool-getWeather') {\n  const weather = part.output;\n}\n\n// ✅ Correct - check for output-available\nif (part.type === 'tool-getWeather' && part.state === 'output-available') {\n  const location = part.input.location;\n  const weather = part.output;\n}\n```\n\n## `part.toolInvocation.args` → `part.input`\n\n```tsx\n// ❌ Incorrect\nif (part.type === 'tool-invocation') {\n  // deprecated: use `part.input` on typed tool parts instead\n  const location = part.toolInvocation.args.location;\n}\n\n// ✅ Correct\nif (\n  part.type === 'tool-getWeather' &&\n  (part.state === 'input-available' || part.state === 'output-available')\n) {\n  const location = part.input.location;\n}\n```\n\n## `part.toolInvocation.result` → `part.output`\n\n```tsx\n// ❌ Incorrect\nif (part.type === 'tool-invocation') {\n  // deprecated: use `part.output` on typed tool parts instead\n  const weather = part.toolInvocation.result;\n}\n\n// ✅ Correct\nif (part.type === 'tool-getWeather' && part.state === 'output-available') {\n  const weather = part.output;\n}\n```\n\n## `part.toolInvocation.toolCallId` → `part.toolCallId`\n\n```tsx\n// ❌ Incorrect\nif (part.type === 'tool-invocation') {\n  // deprecated: use `part.toolCallId` on typed tool parts instead\n  const id = part.toolInvocation.toolCallId;\n}\n\n// ✅ Correct\nif (part.type === 'tool-getWeather') {\n  const id = part.toolCallId;\n}\n```\n\n## Tool invocation states renamed\n\n```tsx\n// ❌ Incorrect\nswitch (part.toolInvocation.state) {\n  case 'partial-call': // deprecated: use `input-streaming` instead\n    return <div>Loading...</div>;\n  case 'call': // deprecated: use `input-available` instead\n    return <div>Executing...</div>;\n  case 'result': // deprecated: use `output-available` instead\n    return <div>Done</div>;\n}\n\n// ✅ Correct\nswitch (part.state) {\n  case 'input-streaming':\n    return <div>Loading...</div>;\n  case 'input-available':\n    return <div>Executing...</div>;\n  case 'output-available':\n    return <div>Done</div>;\n}\n```\n\n## `addToolResult` → `addToolOutput`\n\n```tsx\n// ❌ Incorrect\naddToolResult({\n  // deprecated: use `addToolOutput` instead\n  toolCallId: part.toolInvocation.toolCallId,\n  result: 'Yes, confirmed.', // deprecated: use `output` instead\n});\n\n// ✅ Correct\naddToolOutput({\n  tool: 'askForConfirmation',\n  toolCallId: part.toolCallId,\n  output: 'Yes, confirmed.',\n});\n```\n\n## `messages` → `uiMessages` in `createAgentUIStreamResponse`\n\n```typescript\n// ❌ Incorrect\nreturn createAgentUIStreamResponse({\n  agent: myAgent,\n  messages, // incorrect: use `uiMessages` instead\n});\n\n// ✅ Correct\nreturn createAgentUIStreamResponse({\n  agent: myAgent,\n  uiMessages: messages,\n});\n```\n",".agents/skills/ai-sdk/references/devtools.md":"---\ntitle: AI SDK DevTools\ndescription: Debug AI SDK calls by inspecting captured runs and steps.\n---\n\n# AI SDK DevTools\n\n## Why Use DevTools\n\nDevTools captures all AI SDK calls (`generateText`, `streamText`, `ToolLoopAgent`) to a local JSON file. This lets you inspect LLM requests, responses, tool calls, and multi-step interactions without manually logging.\n\n## Setup\n\nRequires AI SDK 6. Install `@ai-sdk/devtools` using your project's package manager.\n\nWrap your model with the middleware:\n\n```ts\nimport { wrapLanguageModel, gateway } from 'ai';\nimport { devToolsMiddleware } from '@ai-sdk/devtools';\n\nconst model = wrapLanguageModel({\n  model: gateway('anthropic/claude-sonnet-4.5'),\n  middleware: devToolsMiddleware(),\n});\n```\n\n## Viewing Captured Data\n\nAll runs and steps are saved to:\n\n```\n.devtools/generations.json\n```\n\nRead this file directly to inspect captured data:\n\n```bash\ncat .devtools/generations.json | jq\n```\n\nOr launch the web UI:\n\n```bash\nnpx @ai-sdk/devtools\n# Open http://localhost:4983\n```\n\n## Data Structure\n\n- **Run**: A complete multi-step interaction grouped by initial prompt\n- **Step**: A single LLM call within a run (includes input, output, tool calls, token usage)\n",".agents/skills/ai-sdk/references/type-safe-agents.md":"---\ntitle: Type-Safe useChat with Agents\ndescription: Build end-to-end type-safe agents by inferring UIMessage types from your agent definition.\n---\n\n# Type-Safe useChat with Agents\n\nBuild end-to-end type-safe agents by inferring `UIMessage` types from your agent definition for type-safe UI rendering with `useChat`.\n\n## Recommended Structure\n\n```\nlib/\n  agents/\n    my-agent.ts       # Agent definition + type export\n  tools/\n    weather-tool.ts   # Individual tool definitions\n    calculator-tool.ts\n```\n\n## Define Tools\n\n```ts\n// lib/tools/weather-tool.ts\nimport { tool } from 'ai';\nimport { z } from 'zod';\n\nexport const weatherTool = tool({\n  description: 'Get current weather for a location',\n  inputSchema: z.object({\n    location: z.string().describe('City name'),\n  }),\n  execute: async ({ location }) => {\n    return { temperature: 72, condition: 'sunny', location };\n  },\n});\n```\n\n## Define Agent and Export Type\n\n```ts\n// lib/agents/my-agent.ts\nimport { ToolLoopAgent, InferAgentUIMessage } from 'ai';\nimport { weatherTool } from '../tools/weather-tool';\nimport { calculatorTool } from '../tools/calculator-tool';\n\nexport const myAgent = new ToolLoopAgent({\n  model: 'anthropic/claude-sonnet-4',\n  instructions: 'You are a helpful assistant.',\n  tools: {\n    weather: weatherTool,\n    calculator: calculatorTool,\n  },\n});\n\n// Infer the UIMessage type from the agent\nexport type MyAgentUIMessage = InferAgentUIMessage<typeof myAgent>;\n```\n\n### With Custom Metadata\n\n```ts\n// lib/agents/my-agent.ts\nimport { z } from 'zod';\n\nconst metadataSchema = z.object({\n  createdAt: z.number(),\n  model: z.string().optional(),\n});\n\ntype MyMetadata = z.infer<typeof metadataSchema>;\n\nexport type MyAgentUIMessage = InferAgentUIMessage<typeof myAgent, MyMetadata>;\n```\n\n## Use with `useChat`\n\n```tsx\n// app/chat.tsx\nimport { useChat } from '@ai-sdk/react';\nimport type { MyAgentUIMessage } from '@/lib/agents/my-agent';\n\nexport function Chat() {\n  const { messages } = useChat<MyAgentUIMessage>();\n\n  return (\n    <div>\n      {messages.map(message => (\n        <Message key={message.id} message={message} />\n      ))}\n    </div>\n  );\n}\n```\n\n## Rendering Parts with Type Safety\n\nTool parts are typed as `tool-{toolName}` based on your agent's tools:\n\n```tsx\nfunction Message({ message }: { message: MyAgentUIMessage }) {\n  return (\n    <div>\n      {message.parts.map((part, i) => {\n        switch (part.type) {\n          case 'text':\n            return <p key={i}>{part.text}</p>;\n\n          case 'tool-weather':\n            // part.input and part.output are fully typed\n            if (part.state === 'output-available') {\n              return (\n                <div key={i}>\n                  Weather in {part.input.location}: {part.output.temperature}F\n                </div>\n              );\n            }\n            return <div key={i}>Loading weather...</div>;\n\n          case 'tool-calculator':\n            // TypeScript knows this is the calculator tool\n            return <div key={i}>Calculating...</div>;\n\n          default:\n            return null;\n        }\n      })}\n    </div>\n  );\n}\n```\n\nThe `part.type` discriminant narrows the type, giving you autocomplete and type checking for `input` and `output` based on each tool's schema.\n\n## Splitting Tool Rendering into Components\n\nWhen rendering many tools, you may want to split each tool into its own component. Use `UIToolInvocation<TOOL>` to derive a typed invocation from your tool and export it alongside the tool definition:\n\n```ts\n// lib/tools/weather-tool.ts\nimport { tool, UIToolInvocation } from 'ai';\nimport { z } from 'zod';\n\nexport const weatherTool = tool({\n  description: 'Get current weather for a location',\n  inputSchema: z.object({\n    location: z.string().describe('City name'),\n  }),\n  execute: async ({ location }) => {\n    return { temperature: 72, condition: 'sunny', location };\n  },\n});\n\n// Export the invocation type for use in UI components\nexport type WeatherToolInvocation = UIToolInvocation<typeof weatherTool>;\n```\n\nThen import only the type in your component:\n\n```tsx\n// components/weather-tool.tsx\nimport type { WeatherToolInvocation } from '@/lib/tools/weather-tool';\n\nexport function WeatherToolComponent({\n  invocation,\n}: {\n  invocation: WeatherToolInvocation;\n}) {\n  // invocation.input and invocation.output are fully typed\n  if (invocation.state === 'output-available') {\n    return (\n      <div>\n        Weather in {invocation.input.location}: {invocation.output.temperature}F\n      </div>\n    );\n  }\n  return <div>Loading weather for {invocation.input?.location}...</div>;\n}\n```\n\nUse the component in your message renderer:\n\n```tsx\nfunction Message({ message }: { message: MyAgentUIMessage }) {\n  return (\n    <div>\n      {message.parts.map((part, i) => {\n        switch (part.type) {\n          case 'text':\n            return <p key={i}>{part.text}</p>;\n          case 'tool-weather':\n            return <WeatherToolComponent key={i} invocation={part} />;\n          case 'tool-calculator':\n            return <CalculatorToolComponent key={i} invocation={part} />;\n          default:\n            return null;\n        }\n      })}\n    </div>\n  );\n}\n```\n\nThis approach keeps your tool rendering logic organized while maintaining full type safety, without needing to import the tool implementation into your UI components.\n",".agents/skills/baseline-ui/SKILL.md":"---\nname: baseline-ui\ndescription: Validates animation durations, enforces typography scale, checks component accessibility, and prevents layout anti-patterns in Tailwind CSS projects. Use when building UI components, reviewing CSS utilities, styling React views, or enforcing design consistency.\n---\n\n# Baseline UI\n\nEnforces an opinionated UI baseline to prevent AI-generated interface slop.\n\n## How to use\n\n- `/baseline-ui`\n  Apply these constraints to any UI work in this conversation.\n\n- `/baseline-ui <file>`\n  Review the file against all constraints below and output:\n  - violations (quote the exact line/snippet)\n  - why it matters (1 short sentence)\n  - a concrete fix (code-level suggestion)\n\n## Stack\n\n- MUST use Tailwind CSS defaults unless custom values already exist or are explicitly requested\n- MUST use `motion/react` (formerly `framer-motion`) when JavaScript animation is required\n- SHOULD use `tw-animate-css` for entrance and micro-animations in Tailwind CSS\n- MUST use `cn` utility (`clsx` + `tailwind-merge`) for class logic\n\n## Components\n\n- MUST use accessible component primitives for anything with keyboard or focus behavior (`Base UI`, `React Aria`, `Radix`)\n- MUST use the project’s existing component primitives first\n- NEVER mix primitive systems within the same interaction surface\n- SHOULD prefer [`Base UI`](https://base-ui.com/react/components) for new primitives if compatible with the stack\n- MUST add an `aria-label` to icon-only buttons\n- NEVER rebuild keyboard or focus behavior by hand unless explicitly requested\n\n## Interaction\n\n- MUST use an `AlertDialog` for destructive or irreversible actions\n- SHOULD use structural skeletons for loading states\n- NEVER use `h-screen`, use `h-dvh`\n- MUST respect `safe-area-inset` for fixed elements\n- MUST show errors next to where the action happens\n- NEVER block paste in `input` or `textarea` elements\n\n## Animation\n\n- NEVER add animation unless it is explicitly requested\n- MUST animate only compositor props (`transform`, `opacity`)\n- NEVER animate layout properties (`width`, `height`, `top`, `left`, `margin`, `padding`)\n- SHOULD avoid animating paint properties (`background`, `color`) except for small, local UI (text, icons)\n- SHOULD use `ease-out` on entrance\n- NEVER exceed `200ms` for interaction feedback\n- MUST pause looping animations when off-screen\n- SHOULD respect `prefers-reduced-motion`\n- NEVER introduce custom easing curves unless explicitly requested\n- SHOULD avoid animating large images or full-screen surfaces\n\n## Typography\n\n- MUST use `text-balance` for headings and `text-pretty` for body/paragraphs\n- MUST use `tabular-nums` for data\n- SHOULD use `truncate` or `line-clamp` for dense UI\n- NEVER modify `letter-spacing` (`tracking-*`) unless explicitly requested\n\n## Layout\n\n- MUST use a fixed `z-index` scale (no arbitrary `z-*`)\n- SHOULD use `size-*` for square elements instead of `w-*` + `h-*`\n\n## Performance\n\n- NEVER animate large `blur()` or `backdrop-filter` surfaces\n- NEVER apply `will-change` outside an active animation\n- NEVER use `useEffect` for anything that can be expressed as render logic\n\n## Design\n\n- NEVER use gradients unless explicitly requested\n- NEVER use purple or multicolor gradients\n- NEVER use glow effects as primary affordances\n- SHOULD use Tailwind CSS default shadow scale unless explicitly requested\n- MUST give empty states one clear next action\n- SHOULD limit accent color usage to one per view\n- SHOULD use existing theme or Tailwind CSS color tokens before introducing new ones\n",".agents/skills/chat-sdk/SKILL.md":"---\nname: chat-sdk\ndescription: >\n  Build multi-platform chat bots with Chat SDK (`chat` npm package). Use when developers want to\n  (1) Build a Slack, Teams, Google Chat, Discord, Telegram, GitHub, Linear, or WhatsApp bot,\n  (2) Use Chat SDK to handle mentions, direct messages, subscribed threads, reactions, slash\n  commands, cards, modals, files, or AI streaming,\n  (3) Set up webhook routes or multi-adapter bots,\n  (4) Send rich cards or streamed AI responses to chat platforms,\n  (5) Build or maintain a custom adapter or state adapter.\n  Triggers on \"chat sdk\", \"chat bot\", \"slack bot\", \"teams bot\", \"google chat bot\", \"discord bot\",\n  \"telegram bot\", \"whatsapp bot\", \"@chat-adapter\", \"@chat-adapter/state-\", \"custom adapter\",\n  \"state adapter\", \"build adapter\", and building bots that work across multiple chat platforms.\n---\n\n# Chat SDK\n\nUnified TypeScript SDK for building chat bots across Slack, Teams, Google Chat, Discord, Telegram, GitHub, Linear, and WhatsApp. Write bot logic once, deploy everywhere.\n\n## Start with published sources\n\nWhen Chat SDK is installed in a user project, inspect the published files that ship in `node_modules`:\n\n```\nnode_modules/chat/docs/                    # bundled docs\nnode_modules/chat/dist/index.d.ts          # core API types\nnode_modules/chat/dist/jsx-runtime.d.ts    # JSX runtime types\nnode_modules/chat/docs/contributing/       # adapter-authoring docs\nnode_modules/chat/docs/guides/             # framework/platform guides\n```\n\nIf one of the paths below does not exist, that package is not installed in the project yet.\n\nRead these before writing code:\n- `node_modules/chat/docs/getting-started.mdx` — install and setup\n- `node_modules/chat/docs/usage.mdx` — `Chat` config and lifecycle\n- `node_modules/chat/docs/handling-events.mdx` — event routing and handlers\n- `node_modules/chat/docs/threads-messages-channels.mdx` — thread/channel/message model\n- `node_modules/chat/docs/posting-messages.mdx` — post, edit, delete, schedule\n- `node_modules/chat/docs/streaming.mdx` — AI SDK integration and streaming semantics\n- `node_modules/chat/docs/cards.mdx` — JSX cards\n- `node_modules/chat/docs/actions.mdx` — button/select interactions\n- `node_modules/chat/docs/modals.mdx` — modal submit/close flows\n- `node_modules/chat/docs/slash-commands.mdx` — slash command routing\n- `node_modules/chat/docs/direct-messages.mdx` — DM behavior and `openDM()`\n- `node_modules/chat/docs/files.mdx` — attachments/uploads\n- `node_modules/chat/docs/state.mdx` — persistence, locking, dedupe\n- `node_modules/chat/docs/adapters.mdx` — cross-platform feature matrix\n- `node_modules/chat/docs/api/chat.mdx` — exact `Chat` API\n- `node_modules/chat/docs/api/thread.mdx` — exact `Thread` API\n- `node_modules/chat/docs/api/message.mdx` — exact `Message` API\n- `node_modules/chat/docs/api/modals.mdx` — modal element and event details\n\nFor the specific adapter or state package you are using, inspect that installed package's `dist/index.d.ts` export surface in `node_modules`.\n\n## Quick start\n\n```typescript\nimport { Chat } from \"chat\";\nimport { createSlackAdapter } from \"@chat-adapter/slack\";\nimport { createRedisState } from \"@chat-adapter/state-redis\";\n\nconst bot = new Chat({\n  userName: \"mybot\",\n  adapters: {\n    slack: createSlackAdapter(),\n  },\n  state: createRedisState(),\n  dedupeTtlMs: 600_000,\n});\n\nbot.onNewMention(async (thread) => {\n  await thread.subscribe();\n  await thread.post(\"Hello! I'm listening to this thread.\");\n});\n\nbot.onSubscribedMessage(async (thread, message) => {\n  await thread.post(`You said: ${message.text}`);\n});\n```\n\n## Core concepts\n\n- **Chat** — main entry point; coordinates adapters, routing, locks, and state\n- **Adapters** — platform-specific integrations for Slack, Teams, Google Chat, Discord, Telegram, GitHub, Linear, and WhatsApp\n- **State adapters** — persistence for subscriptions, locks, dedupe, and thread state\n- **Thread** — conversation context with `post()`, `stream()`, `subscribe()`, `setState()`, `startTyping()`\n- **Message** — normalized content with `text`, `formatted`, attachments, author info, and platform `raw`\n- **Channel** — container for threads and top-level posts\n\n## Event handlers\n\n| Handler | Trigger |\n|---------|---------|\n| `onNewMention` | Bot @-mentioned in an unsubscribed thread |\n| `onDirectMessage` | New DM in an unsubscribed DM thread |\n| `onSubscribedMessage` | Any message in a subscribed thread |\n| `onNewMessage(regex)` | Regex match in an unsubscribed thread |\n| `onReaction(emojis?)` | Emoji added or removed |\n| `onAction(actionIds?)` | Button clicks and select/radio interactions |\n| `onModalSubmit(callbackId?)` | Modal form submitted |\n| `onModalClose(callbackId?)` | Modal dismissed/cancelled |\n| `onSlashCommand(commands?)` | Slash command invocation |\n| `onAssistantThreadStarted` | Slack assistant thread opened |\n| `onAssistantContextChanged` | Slack assistant context changed |\n| `onAppHomeOpened` | Slack App Home opened |\n| `onMemberJoinedChannel` | Slack member joined channel event |\n\nRead `node_modules/chat/docs/handling-events.mdx`, `node_modules/chat/docs/actions.mdx`, `node_modules/chat/docs/modals.mdx`, and `node_modules/chat/docs/slash-commands.mdx` before wiring handlers. `onDirectMessage` behavior is documented in `node_modules/chat/docs/direct-messages.mdx`.\n\n## Streaming\n\nPass any `AsyncIterable<string>` to `thread.post()` or `thread.stream()`. For AI SDK, prefer `result.fullStream` over `result.textStream` when available so step boundaries are preserved.\n\n```typescript\nimport { ToolLoopAgent } from \"ai\";\n\nconst agent = new ToolLoopAgent({ model: \"anthropic/claude-4.5-sonnet\" });\n\nbot.onNewMention(async (thread, message) => {\n  const result = await agent.stream({ prompt: message.text });\n  await thread.post(result.fullStream);\n});\n```\n\nKey details:\n- `streamingUpdateIntervalMs` controls post+edit fallback cadence\n- `fallbackStreamingPlaceholderText` defaults to `\"...\"`; set `null` to disable\n- Structured `StreamChunk` support is Slack-only; other adapters ignore non-text chunks\n\n## Cards and modals (JSX)\n\nSet `jsxImportSource: \"chat\"` in `tsconfig.json`.\n\nCard components:\n- `Card`, `CardText`, `Section`, `Fields`, `Field`, `Button`, `CardLink`, `LinkButton`, `Actions`, `Select`, `SelectOption`, `RadioSelect`, `Table`, `Image`, `Divider`\n\nModal components:\n- `Modal`, `TextInput`, `Select`, `SelectOption`, `RadioSelect`\n\n```tsx\nawait thread.post(\n  <Card title=\"Order #1234\">\n    <CardText>Your order has been received.</CardText>\n    <Actions>\n      <Button id=\"approve\" style=\"primary\">Approve</Button>\n      <Button id=\"reject\" style=\"danger\">Reject</Button>\n    </Actions>\n  </Card>\n);\n```\n\n## Adapter inventory\n\n### Official platform adapters\n\n| Platform | Package | Factory |\n|---------|---------|---------|\n| Slack | `@chat-adapter/slack` | `createSlackAdapter` |\n| Microsoft Teams | `@chat-adapter/teams` | `createTeamsAdapter` |\n| Google Chat | `@chat-adapter/gchat` | `createGoogleChatAdapter` |\n| Discord | `@chat-adapter/discord` | `createDiscordAdapter` |\n| GitHub | `@chat-adapter/github` | `createGitHubAdapter` |\n| Linear | `@chat-adapter/linear` | `createLinearAdapter` |\n| Telegram | `@chat-adapter/telegram` | `createTelegramAdapter` |\n| WhatsApp Business Cloud | `@chat-adapter/whatsapp` | `createWhatsAppAdapter` |\n\n### Official state adapters\n\n| State backend | Package | Factory |\n|--------------|---------|---------|\n| Redis | `@chat-adapter/state-redis` | `createRedisState` |\n| ioredis | `@chat-adapter/state-ioredis` | `createIoRedisState` |\n| PostgreSQL | `@chat-adapter/state-pg` | `createPostgresState` |\n| Memory | `@chat-adapter/state-memory` | `createMemoryState` |\n\n### Community adapters\n\n- `chat-state-cloudflare-do`\n- `@beeper/chat-adapter-matrix`\n- `chat-adapter-imessage`\n- `@bitbasti/chat-adapter-webex`\n- `@resend/chat-sdk-adapter`\n- `chat-adapter-baileys`\n\n### Coming-soon platform entries\n\n- Instagram\n- Signal\n- X\n- Messenger\n\n## Building a custom adapter\n\nRead these published docs first:\n- `node_modules/chat/docs/contributing/building.mdx`\n- `node_modules/chat/docs/contributing/testing.mdx`\n- `node_modules/chat/docs/contributing/publishing.mdx`\n\nAlso inspect:\n- `node_modules/chat/dist/index.d.ts` — `Adapter` and related interfaces\n- `node_modules/@chat-adapter/shared/dist/index.d.ts` — shared errors and utilities\n- Installed official adapter `dist/index.d.ts` files — reference implementations for config and APIs\n\nA custom adapter needs request verification, webhook parsing, message/thread/channel operations, ID encoding/decoding, and a format converter. Use `BaseFormatConverter` from `chat` and shared utilities from `@chat-adapter/shared`.\n\n## Webhook setup\n\nEach registered adapter exposes `bot.webhooks.<name>`. Wire those directly to your HTTP framework routes. See `node_modules/chat/docs/guides/slack-nextjs.mdx` and `node_modules/chat/docs/guides/discord-nuxt.mdx` for framework-specific route patterns.\n",".agents/skills/code-review/SKILL.md":"---\nname: code-review\ndescription: Reviews code changes and provides actionable feedback. Use when the user asks to review a PR, diff, commit, or code changes. Triggers on \"/review\", \"review this PR\", \"review my changes\", \"code review\".\n---\n\nYou are a code reviewer. Your job is to review code changes and provide actionable feedback.\n\n## Determining What to Review\n\nBased on the input provided, determine which type of review to perform:\n\n1. **No arguments (default)**: Review all uncommitted changes\n   - Run: `git diff` for unstaged changes\n   - Run: `git diff --cached` for staged changes\n\n2. **Commit hash** (40-char SHA or short hash): Review that specific commit\n   - Run: `git show $ARGUMENTS`\n\n3. **Branch name**: Compare current branch to the specified branch\n   - Run: `git diff $ARGUMENTS...HEAD`\n\n4. **PR URL or number** (contains \"github.com\" or \"pull\" or looks like a PR number): Review the pull request\n   - Run: `gh pr view $ARGUMENTS` to get PR context\n   - Run: `gh pr diff $ARGUMENTS` to get the diff\n\nUse best judgement when processing input.\n\n---\n\n## Gathering Context\n\n**Diffs alone are not enough.** After getting the diff, read the entire file(s) being modified to understand the full context. Code that looks wrong in isolation may be correct given surrounding logic—and vice versa.\n\n- Use the diff to identify which files changed\n- Read the full file to understand existing patterns, control flow, and error handling\n- When changes touch inputs, auth, storage, networking, rendering, or secrets, trace the trust boundary instead of reviewing the code in isolation\n- Check for existing style guide or conventions files (CONVENTIONS.md, AGENTS.md, .editorconfig, etc.)\n\n---\n\n## What to Look For\n\n**Bugs** - Your primary focus.\n- Logic errors, off-by-one mistakes, incorrect conditionals\n- If-else guards: missing guards, incorrect branching, unreachable code paths\n- Edge cases: null/empty/undefined inputs, error conditions, race conditions\n- Broken error handling that swallows failures, throws unexpectedly or returns error types that are not caught.\n\n**Security / Safety** - Treat this as a first-class review concern, not an afterthought.\n- Assume changed code may be reachable by untrusted users or hostile input unless you can verify otherwise\n- Look for injection, XSS, auth/authz bypass, CSRF, SSRF, open redirects, path traversal, unsafe file access, secret/token exposure, privilege escalation, insecure defaults, and tenant/data isolation leaks\n- Check that validation and authorization happen at the real boundary, not only in the UI or caller\n- Verify sensitive operations fail closed, do not log secrets, and do not expand access beyond the intended actor/resource scope\n- Prefer flagging realistic exploit paths over generic \"security concern\" comments; explain the attacker-controlled input, boundary, and impact\n\n**Structure** - Does the code fit the codebase?\n- Does it follow existing patterns and conventions?\n- Are there established abstractions it should use but doesn't?\n- Excessive nesting that could be flattened with early returns or extraction\n\n**Performance** - Only flag if obviously problematic.\n- O(n²) on unbounded data, N+1 queries, blocking I/O on hot paths\n\n---\n\n## Before You Flag Something\n\n**Be certain.** If you're going to call something a bug, you need to be confident it actually is one.\n\n- Only review the changes - do not review pre-existing code that wasn't modified\n- Don't flag something as a bug if you're unsure - investigate first\n- Don't invent hypothetical problems - if an edge case matters, explain the realistic scenario where it breaks\n- For security findings, describe the concrete exploit path or trust-boundary failure instead of vague risk language\n- If you need more context to be sure, use the tools below to get it\n\n**Don't be a zealot about style.** When checking code against conventions:\n\n- Verify the code is *actually* in violation. Don't complain about else statements if early returns are already being used correctly.\n- Some \"violations\" are acceptable when they're the simplest option. A `let` statement is fine if the alternative is convoluted.\n- Excessive nesting is a legitimate concern regardless of other style choices.\n- Don't flag style preferences as issues unless they clearly violate established project conventions.\n\n---\n\n## Tools\n\nUse these to inform your review:\n\n- **Explore agent** - Find how existing code handles similar problems. Check patterns, conventions, and prior art before claiming something doesn't fit.\n\nIf you're uncertain about something and can't verify it with these tools, say \"I'm not sure about X\" rather than flagging it as a definite issue.\n\n---\n\n## Output\n\n1. If there is a bug, be direct and clear about why it is a bug.\n2. Clearly communicate severity of issues. Do not overstate severity.\n3. Critiques should clearly and explicitly communicate the scenarios, environments, or inputs that are necessary for the bug to arise. The comment should immediately indicate that the issue's severity depends on these factors.\n4. For security findings, explicitly state the attacker-controlled input, missing control, and concrete impact.\n5. Your tone should be matter-of-fact and not accusatory or overly positive. It should read as a helpful AI assistant suggestion without sounding too much like a human reviewer.\n6. Write so the reader can quickly understand the issue without reading too closely.\n7. AVOID flattery, do not give any comments that are not helpful to the reader. Avoid phrasing like \"Great job ...\", \"Thanks for ...\".\n",".agents/skills/deploy-open-harness/SKILL.md":"---\nname: deploy-open-harness\ndescription: Guides a user through collecting the credentials needed to deploy their own copy of Open Harness, deploying this repo on Vercel, and completing first-run setup. Use for requests about deploying, self-hosting, configuring credentials, or getting started with a fork of this app.\n---\n\nYou are helping a user deploy their own copy of Open Harness.\n\nBase your guidance on the current codebase, not on older Harness-era setup assumptions.\n\n## First rule: verify current requirements from the repo\n\nBefore giving deployment advice, read these files if you have not already:\n\n- `README.md`\n- `apps/web/.env.example`\n- `apps/web/lib/db/client.ts`\n- `apps/web/lib/jwe/encrypt.ts`\n- `apps/web/lib/crypto.ts`\n- `apps/web/app/api/auth/signin/vercel/route.ts`\n- `apps/web/app/api/auth/vercel/callback/route.ts`\n- `apps/web/app/api/github/app/install/route.ts`\n- `apps/web/app/api/github/app/callback/route.ts`\n- `apps/web/lib/github/app-auth.ts`\n- `apps/web/lib/redis.ts`\n- `apps/web/lib/sandbox/config.ts`\n\nIf the code and the docs disagree, trust the code and say so.\n\nDo not rely on `scripts/setup.sh`.\n\n## Goals\n\nHelp the user:\n\n1. Decide whether they want a minimal deploy or the full GitHub-enabled coding-agent flow.\n2. Collect only the credentials actually required for that scope.\n3. Understand where to obtain each credential.\n4. Deploy this repo on Vercel.\n5. Complete first-run verification.\n6. Leave with a short next-steps checklist.\n\n## Safety rules\n\n- Never ask the user to paste secrets into chat.\n- Tell them where each value belongs, but keep secret values in Vercel project env vars or local env files.\n- Separate blockers for a minimal deploy from blockers for the full GitHub-enabled flow.\n- Be explicit when something is optional.\n\n## Scope the deployment first\n\nStart by determining which path the user wants:\n\n### 1) Minimal deploy\nA working hosted app where the user can deploy it, sign in with Vercel, and use the product without GitHub repo access.\n\n### 2) Full deploy\nEverything in the minimal deploy, plus GitHub account linking, GitHub App installation, private repo access, pushes, and PR creation.\n\nIf the user is unsure, recommend **minimal deploy first**, then layer on GitHub.\n\n## Credential checklist\n\nUse this checklist when guiding the user.\n\n### Required for the app to run\n\n- `POSTGRES_URL`\n- `JWE_SECRET`\n\n### Required for a usable hosted deployment\n\n- `ENCRYPTION_KEY`\n- `NEXT_PUBLIC_VERCEL_APP_CLIENT_ID`\n- `VERCEL_APP_CLIENT_SECRET`\n\n### Required for GitHub-enabled repo flows\n\n- `NEXT_PUBLIC_GITHUB_CLIENT_ID`\n- `GITHUB_CLIENT_SECRET`\n- `GITHUB_APP_ID`\n- `GITHUB_APP_PRIVATE_KEY`\n- `NEXT_PUBLIC_GITHUB_APP_SLUG`\n- `GITHUB_WEBHOOK_SECRET`\n\n### Optional\n\n- `REDIS_URL` or `KV_URL`\n- `VERCEL_PROJECT_PRODUCTION_URL`\n- `NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL`\n- `VERCEL_SANDBOX_BASE_SNAPSHOT_ID`\n- `ELEVENLABS_API_KEY`\n\n## How to explain each credential\n\n### PostgreSQL\nTell the user to create a Postgres database and copy the connection string into `POSTGRES_URL`.\n\n### JWE secret\nExplain that this is required for session encryption.\n\nRecommended generation command:\n\n```bash\nopenssl rand -base64 32 | tr '+/' '-_' | tr -d '=\\n'\n```\n\n### Encryption key\nExplain that provider tokens are encrypted at rest and the value must be a 64-character hex string.\n\nRecommended generation command:\n\n```bash\nopenssl rand -hex 32\n```\n\n### Vercel OAuth app\nTell the user to create a Vercel OAuth app and set:\n\n- Callback URL: `https://YOUR_DOMAIN/api/auth/vercel/callback`\n- For local dev: `http://localhost:3000/api/auth/vercel/callback`\n\nStore the credentials as:\n\n- `NEXT_PUBLIC_VERCEL_APP_CLIENT_ID`\n- `VERCEL_APP_CLIENT_SECRET`\n\n### GitHub App\nTell the user they do not need a separate GitHub OAuth app. Open Harness uses the GitHub App's user authorization flow.\n\nTell the user to create a GitHub App and set:\n\n- Homepage URL: `https://YOUR_DOMAIN`\n- Callback URL: `https://YOUR_DOMAIN/api/github/app/callback`\n- Setup URL: `https://YOUR_DOMAIN/api/github/app/callback`\n- For local dev: homepage `http://localhost:3000`, callback/setup `http://localhost:3000/api/github/app/callback`\n\nAlso tell them to:\n\n- enable \"Request user authorization (OAuth) during installation\"\n- use the GitHub App Client ID and Client Secret for `NEXT_PUBLIC_GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET`\n- make the app public if they want org installs to work cleanly\n- generate a webhook secret\n- download/generate the private key\n\nStore the values as:\n\n- `NEXT_PUBLIC_GITHUB_CLIENT_ID`\n- `GITHUB_CLIENT_SECRET`\n- `GITHUB_APP_ID`\n- `GITHUB_APP_PRIVATE_KEY`\n- `NEXT_PUBLIC_GITHUB_APP_SLUG`\n- `GITHUB_WEBHOOK_SECRET`\n\nMention that `GITHUB_APP_PRIVATE_KEY` can be stored either as PEM contents with escaped newlines or as a base64-encoded PEM.\n\n### Redis / KV\nExplain that Redis is optional. It improves resumable streams, stop signaling, and caching, but it is not required for the first deploy.\n\n## Deployment flow\n\nGuide the user through this sequence:\n\n1. Fork the repo.\n2. Import it into Vercel at the repo root.\n3. Add the baseline env vars:\n   - `POSTGRES_URL`\n   - `JWE_SECRET`\n   - `ENCRYPTION_KEY`\n4. Deploy once to get a stable production URL.\n5. Create the Vercel OAuth app using that production URL.\n6. Add `NEXT_PUBLIC_VERCEL_APP_CLIENT_ID` and `VERCEL_APP_CLIENT_SECRET`.\n7. Redeploy.\n8. If the user wants the full GitHub flow, create the GitHub App using the production URL, add the GitHub env vars, and redeploy again.\n9. Optionally add Redis/KV and the production URL vars.\n\nIf the user already has a custom domain ready, it is fine to use that domain from the start instead of the default `vercel.app` production URL.\n\n## First-run verification\n\nFor a minimal deploy, walk the user through:\n\n1. Open the production site.\n2. Sign in with Vercel.\n3. Confirm they land in the app successfully.\n4. Create a session and confirm the basic UI loads.\n\nFor the full deploy, also verify:\n\n1. GitHub account linking works.\n2. GitHub App installation completes.\n3. Installations or repos appear in the UI.\n4. A repo-backed session can start.\n5. The sandbox starts and the agent can work in the repo.\n\nIf something fails, identify the missing credential or callback mismatch instead of giving generic advice.\n\n## Response format\n\nWhen helping a user, prefer this structure:\n\n1. **Target scope** — minimal or full.\n2. **Credential checklist** — grouped into required now vs optional later.\n3. **How to get each missing credential** — short, concrete instructions.\n4. **Deploy steps** — only the next actions the user should take.\n5. **Verification** — what to click/test after deploy.\n6. **Next upgrades** — Redis, GitHub, voice, custom domain, snapshot override, only if relevant.\n\nBe concise. Keep the user moving toward the next unblocker.",".agents/skills/emil-design-eng/SKILL.md":"---\nname: emil-design-eng\ndescription: This skill encodes Emil Kowalski's philosophy on UI polish, component design, animation decisions, and the invisible details that make software feel great.\n---\n\n# Design Engineering\n\nYou are a design engineer with the craft sensibility. You build interfaces where every detail compounds into something that feels right. You understand that in a world where everyone's software is good enough, taste is the differentiator.\n\n## Core Philosophy\n\n### Taste is trained, not innate\n\nGood taste is not personal preference. It is a trained instinct: the ability to see beyond the obvious and recognize what elevates. You develop it by surrounding yourself with great work, thinking deeply about why something feels good, and practicing relentlessly.\n\nWhen building UI, don't just make it work. Study why the best interfaces feel the way they do. Reverse engineer animations. Inspect interactions. Be curious.\n\n### Unseen details compound\n\nMost details users never consciously notice. That is the point. When a feature functions exactly as someone assumes it should, they proceed without giving it a second thought. That is the goal.\n\n> \"All those unseen details combine to produce something that's just stunning, like a thousand barely audible voices all singing in tune.\" - Paul Graham\n\nEvery decision below exists because the aggregate of invisible correctness creates interfaces people love without knowing why.\n\n### Beauty is leverage\n\nPeople select tools based on the overall experience, not just functionality. Good defaults and good animations are real differentiators. Beauty is underutilized in software. Use it as leverage to stand out.\n\n## Review Format (Required)\n\nWhen reviewing UI code, you MUST use a markdown table with Before/After columns. Do NOT use a list with \"Before:\" and \"After:\" on separate lines. Always output an actual markdown table like this:\n\n| Before                                | After                                                             | Why                                                                          |\n| ------------------------------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------- |\n| `transition: all 300ms`               | `transition: transform 200ms ease-out`                            | Specify exact properties; avoid `all`                                        |\n| `transform: scale(0)`                 | `transform: scale(0.95); opacity: 0`                              | Nothing in the real world appears from nothing                               |\n| `ease-in` on dropdown                 | `ease-out` with custom curve                                      | `ease-in` feels sluggish; `ease-out` gives instant feedback                  |\n| No `:active` state on button          | `transform: scale(0.97)` on `:active`                             | Buttons must feel responsive to press                                        |\n| `transform-origin: center` on popover | `transform-origin: var(--radix-popover-content-transform-origin)` | Popovers should scale from their trigger (not modals — modals stay centered) |\n\nWrong format (never do this):\n\n```\nBefore: transition: all 300ms\nAfter: transition: transform 200ms ease-out\n────────────────────────────\nBefore: scale(0)\nAfter: scale(0.95)\n```\n\nCorrect format: A single markdown table with | Before | After | Why | columns, one row per issue found. The \"Why\" column briefly explains the reasoning.\n\n## The Animation Decision Framework\n\nBefore writing any animation code, answer these questions in order:\n\n### 1. Should this animate at all?\n\n**Ask:** How often will users see this animation?\n\n| Frequency                                                   | Decision                     |\n| ----------------------------------------------------------- | ---------------------------- |\n| 100+ times/day (keyboard shortcuts, command palette toggle) | No animation. Ever.          |\n| Tens of times/day (hover effects, list navigation)          | Remove or drastically reduce |\n| Occasional (modals, drawers, toasts)                        | Standard animation           |\n| Rare/first-time (onboarding, feedback forms, celebrations)  | Can add delight              |\n\n**Never animate keyboard-initiated actions.** These actions are repeated hundreds of times daily. Animation makes them feel slow, delayed, and disconnected from the user's actions.\n\nRaycast has no open/close animation. That is the optimal experience for something used hundreds of times a day.\n\n### 2. What is the purpose?\n\nEvery animation must have a clear answer to \"why does this animate?\"\n\nValid purposes:\n\n- **Spatial consistency**: toast enters and exits from the same direction, making swipe-to-dismiss feel intuitive\n- **State indication**: a morphing feedback button shows the state change\n- **Explanation**: a marketing animation that shows how a feature works\n- **Feedback**: a button scales down on press, confirming the interface heard the user\n- **Preventing jarring changes**: elements appearing or disappearing without transition feel broken\n\nIf the purpose is just \"it looks cool\" and the user will see it often, don't animate.\n\n### 3. What easing should it use?\n\nIs the element entering or exiting?\nYes → ease-out (starts fast, feels responsive)\nNo →\nIs it moving/morphing on screen?\nYes → ease-in-out (natural acceleration/deceleration)\nIs it a hover/color change?\nYes → ease\nIs it constant motion (marquee, progress bar)?\nYes → linear\nDefault → ease-out\n\n**Critical: use custom easing curves.** The built-in CSS easings are too weak. They lack the punch that makes animations feel intentional.\n\n```css\n/* Strong ease-out for UI interactions */\n--ease-out: cubic-bezier(0.23, 1, 0.32, 1);\n\n/* Strong ease-in-out for on-screen movement */\n--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);\n\n/* iOS-like drawer curve (from Ionic Framework) */\n--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);\n```\n\n**Never use ease-in for UI animations.** It starts slow, which makes the interface feel sluggish and unresponsive. A dropdown with `ease-in` at 300ms _feels_ slower than `ease-out` at the same 300ms, because ease-in delays the initial movement — the exact moment the user is watching most closely.\n\n**Easing curve resources:** Don't create curves from scratch. Use [easing.dev](https://easing.dev/) or [easings.co](https://easings.co/) to find stronger custom variants of standard easings.\n\n### 4. How fast should it be?\n\n| Element                  | Duration      |\n| ------------------------ | ------------- |\n| Button press feedback    | 100-160ms     |\n| Tooltips, small popovers | 125-200ms     |\n| Dropdowns, selects       | 150-250ms     |\n| Modals, drawers          | 200-500ms     |\n| Marketing/explanatory    | Can be longer |\n\n**Rule: UI animations should stay under 300ms.** A 180ms dropdown feels more responsive than a 400ms one. A faster-spinning spinner makes the app feel like it loads faster, even when the load time is identical.\n\n### Perceived performance\n\nSpeed in animation is not just about feeling snappy — it directly affects how users perceive your app's performance:\n\n- A **fast-spinning spinner** makes loading feel faster (same load time, different perception)\n- A **180ms select** animation feels more responsive than a **400ms** one\n- **Instant tooltips** after the first one is open (skip delay + skip animation) make the whole toolbar feel faster\n\nThe perception of speed matters as much as actual speed. Easing amplifies this: `ease-out` at 200ms _feels_ faster than `ease-in` at 200ms because the user sees immediate movement.\n\n## Spring Animations\n\nSprings feel more natural than duration-based animations because they simulate real physics. They don't have fixed durations — they settle based on physical parameters.\n\n### When to use springs\n\n- Drag interactions with momentum\n- Elements that should feel \"alive\" (like Apple's Dynamic Island)\n- Gestures that can be interrupted mid-animation\n- Decorative mouse-tracking interactions\n\n### Spring-based mouse interactions\n\nTying visual changes directly to mouse position feels artificial because it lacks motion. Use `useSpring` from Motion (formerly Framer Motion) to interpolate value changes with spring-like behavior instead of updating immediately.\n\n```jsx\nimport { useSpring } from \"framer-motion\";\n\n// Without spring: feels artificial, instant\nconst rotation = mouseX * 0.1;\n\n// With spring: feels natural, has momentum\nconst springRotation = useSpring(mouseX * 0.1, {\n  stiffness: 100,\n  damping: 10,\n});\n```\n\nThis works because the animation is **decorative** — it doesn't serve a function. If this were a functional graph in a banking app, no animation would be better. Know when decoration helps and when it hinders.\n\n### Spring configuration\n\n**Apple's approach (recommended — easier to reason about):**\n\n```js\n{ type: \"spring\", duration: 0.5, bounce: 0.2 }\n```\n\n**Traditional physics (more control):**\n\n```js\n{ type: \"spring\", mass: 1, stiffness: 100, damping: 10 }\n```\n\nKeep bounce subtle (0.1-0.3) when used. Avoid bounce in most UI contexts. Use it for drag-to-dismiss and playful interactions.\n\n### Interruptibility advantage\n\nSprings maintain velocity when interrupted — CSS animations and keyframes restart from zero. This makes springs ideal for gestures users might change mid-motion. When you click an expanded item and quickly press Escape, a spring-based animation smoothly reverses from its current position.\n\n## Component Building Principles\n\n### Buttons must feel responsive\n\nAdd `transform: scale(0.97)` on `:active`. This gives instant feedback, making the UI feel like it is truly listening to the user.\n\n```css\n.button {\n  transition: transform 160ms ease-out;\n}\n\n.button:active {\n  transform: scale(0.97);\n}\n```\n\nThis applies to any pressable element. The scale should be subtle (0.95-0.98).\n\n### Never animate from scale(0)\n\nNothing in the real world disappears and reappears completely. Elements animating from `scale(0)` look like they come out of nowhere.\n\nStart from `scale(0.9)` or higher, combined with opacity. Even a barely-visible initial scale makes the entrance feel more natural, like a balloon that has a visible shape even when deflated.\n\n```css\n/* Bad */\n.entering {\n  transform: scale(0);\n}\n\n/* Good */\n.entering {\n  transform: scale(0.95);\n  opacity: 0;\n}\n```\n\n### Make popovers origin-aware\n\nPopovers should scale in from their trigger, not from center. The default `transform-origin: center` is wrong for almost every popover. **Exception: modals.** Modals should keep `transform-origin: center` because they are not anchored to a specific trigger — they appear centered in the viewport.\n\n```css\n/* Radix UI */\n.popover {\n  transform-origin: var(--radix-popover-content-transform-origin);\n}\n\n/* Base UI */\n.popover {\n  transform-origin: var(--transform-origin);\n}\n```\n\nWhether the user notices the difference individually does not matter. In the aggregate, unseen details become visible. They compound.\n\n### Tooltips: skip delay on subsequent hovers\n\nTooltips should delay before appearing to prevent accidental activation. But once one tooltip is open, hovering over adjacent tooltips should open them instantly with no animation. This feels faster without defeating the purpose of the initial delay.\n\n```css\n.tooltip {\n  transition:\n    transform 125ms ease-out,\n    opacity 125ms ease-out;\n  transform-origin: var(--transform-origin);\n}\n\n.tooltip[data-starting-style],\n.tooltip[data-ending-style] {\n  opacity: 0;\n  transform: scale(0.97);\n}\n\n/* Skip animation on subsequent tooltips */\n.tooltip[data-instant] {\n  transition-duration: 0ms;\n}\n```\n\n### Use CSS transitions over keyframes for interruptible UI\n\nCSS transitions can be interrupted and retargeted mid-animation. Keyframes restart from zero. For any interaction that can be triggered rapidly (adding toasts, toggling states), transitions produce smoother results.\n\n```css\n/* Interruptible - good for UI */\n.toast {\n  transition: transform 400ms ease;\n}\n\n/* Not interruptible - avoid for dynamic UI */\n@keyframes slideIn {\n  from {\n    transform: translateY(100%);\n  }\n  to {\n    transform: translateY(0);\n  }\n}\n```\n\n### Use blur to mask imperfect transitions\n\nWhen a crossfade between two states feels off despite trying different easings and durations, add subtle `filter: blur(2px)` during the transition.\n\n**Why blur works:** Without blur, you see two distinct objects during a crossfade — the old state and the new state overlapping. This looks unnatural. Blur bridges the visual gap by blending the two states together, tricking the eye into perceiving a single smooth transformation instead of two objects swapping.\n\nCombine blur with scale-on-press (`scale(0.97)`) for a polished button state transition:\n\n```css\n.button {\n  transition: transform 160ms ease-out;\n}\n\n.button:active {\n  transform: scale(0.97);\n}\n\n.button-content {\n  transition:\n    filter 200ms ease,\n    opacity 200ms ease;\n}\n\n.button-content.transitioning {\n  filter: blur(2px);\n  opacity: 0.7;\n}\n```\n\nKeep blur under 20px. Heavy blur is expensive, especially in Safari.\n\n### Animate enter states with @starting-style\n\nThe modern CSS way to animate element entry without JavaScript:\n\n```css\n.toast {\n  opacity: 1;\n  transform: translateY(0);\n  transition:\n    opacity 400ms ease,\n    transform 400ms ease;\n\n  @starting-style {\n    opacity: 0;\n    transform: translateY(100%);\n  }\n}\n```\n\nThis replaces the common React pattern of using `useEffect` to set `mounted: true` after initial render. Use `@starting-style` when browser support allows; fall back to the `data-mounted` attribute pattern otherwise.\n\n```jsx\n// Legacy pattern (still works everywhere)\nuseEffect(() => {\n  setMounted(true);\n}, []);\n// <div data-mounted={mounted}>\n```\n\n## CSS Transform Mastery\n\n### translateY with percentages\n\nPercentage values in `translate()` are relative to the element's own size. Use `translateY(100%)` to move an element by its own height, regardless of actual dimensions. This is how Sonner positions toasts and how Vaul hides the drawer before animating in.\n\n```css\n/* Works regardless of drawer height */\n.drawer-hidden {\n  transform: translateY(100%);\n}\n\n/* Works regardless of toast height */\n.toast-enter {\n  transform: translateY(-100%);\n}\n```\n\nPrefer percentages over hardcoded pixel values. They are less error-prone and adapt to content.\n\n### scale() scales children too\n\nUnlike `width`/`height`, `scale()` also scales an element's children. When scaling a button on press, the font size, icons, and content scale proportionally. This is a feature, not a bug.\n\n### 3D transforms for depth\n\n`rotateX()`, `rotateY()` with `transform-style: preserve-3d` create real 3D effects in CSS. Orbiting animations, coin flips, and depth effects are all possible without JavaScript.\n\n```css\n.wrapper {\n  transform-style: preserve-3d;\n}\n\n@keyframes orbit {\n  from {\n    transform: translate(-50%, -50%) rotateY(0deg) translateZ(72px)\n      rotateY(360deg);\n  }\n  to {\n    transform: translate(-50%, -50%) rotateY(360deg) translateZ(72px)\n      rotateY(0deg);\n  }\n}\n```\n\n### transform-origin\n\nEvery element has an anchor point from which transforms execute. The default is center. Set it to match where the trigger lives for origin-aware interactions.\n\n## clip-path for Animation\n\n`clip-path` is not just for shapes. It is one of the most powerful animation tools in CSS.\n\n### The inset shape\n\n`clip-path: inset(top right bottom left)` defines a rectangular clipping region. Each value \"eats\" into the element from that side.\n\n```css\n/* Fully hidden from right */\n.hidden {\n  clip-path: inset(0 100% 0 0);\n}\n\n/* Fully visible */\n.visible {\n  clip-path: inset(0 0 0 0);\n}\n\n/* Reveal from left to right */\n.overlay {\n  clip-path: inset(0 100% 0 0);\n  transition: clip-path 200ms ease-out;\n}\n.button:active .overlay {\n  clip-path: inset(0 0 0 0);\n  transition: clip-path 2s linear;\n}\n```\n\n### Tabs with perfect color transitions\n\nDuplicate the tab list. Style the copy as \"active\" (different background, different text color). Clip the copy so only the active tab is visible. Animate the clip on tab change. This creates a seamless color transition that timing individual color transitions can never achieve.\n\n### Hold-to-delete pattern\n\nUse `clip-path: inset(0 100% 0 0)` on a colored overlay. On `:active`, transition to `inset(0 0 0 0)` over 2s with linear timing. On release, snap back with 200ms ease-out. Add `scale(0.97)` on the button for press feedback.\n\n### Image reveals on scroll\n\nStart with `clip-path: inset(0 0 100% 0)` (hidden from bottom). Animate to `inset(0 0 0 0)` when the element enters the viewport. Use `IntersectionObserver` or Framer Motion's `useInView` with `{ once: true, margin: \"-100px\" }`.\n\n### Comparison sliders\n\nOverlay two images. Clip the top one with `clip-path: inset(0 50% 0 0)`. Adjust the right inset value based on drag position. No extra DOM elements needed, fully hardware-accelerated.\n\n## Gesture and Drag Interactions\n\n### Momentum-based dismissal\n\nDon't require dragging past a threshold. Calculate velocity: `Math.abs(dragDistance) / elapsedTime`. If velocity exceeds ~0.11, dismiss regardless of distance. A quick flick should be enough.\n\n```js\nconst timeTaken = new Date().getTime() - dragStartTime.current.getTime();\nconst velocity = Math.abs(swipeAmount) / timeTaken;\n\nif (Math.abs(swipeAmount) >= SWIPE_THRESHOLD || velocity > 0.11) {\n  dismiss();\n}\n```\n\n### Damping at boundaries\n\nWhen a user drags past the natural boundary (e.g., dragging a drawer up when already at top), apply damping. The more they drag, the less the element moves. Things in real life don't suddenly stop; they slow down first.\n\n### Pointer capture for drag\n\nOnce dragging starts, set the element to capture all pointer events. This ensures dragging continues even if the pointer leaves the element bounds.\n\n### Multi-touch protection\n\nIgnore additional touch points after the initial drag begins. Without this, switching fingers mid-drag causes the element to jump to the new position.\n\n```js\nfunction onPress() {\n  if (isDragging) return;\n  // Start drag...\n}\n```\n\n### Friction instead of hard stops\n\nInstead of preventing upward drag entirely, allow it with increasing friction. It feels more natural than hitting an invisible wall.\n\n## Performance Rules\n\n### Only animate transform and opacity\n\nThese properties skip layout and paint, running on the GPU. Animating `padding`, `margin`, `height`, or `width` triggers all three rendering steps.\n\n### CSS variables are inheritable\n\nChanging a CSS variable on a parent recalculates styles for all children. In a drawer with many items, updating `--swipe-amount` on the container causes expensive style recalculation. Update `transform` directly on the element instead.\n\n```js\n// Bad: triggers recalc on all children\nelement.style.setProperty(\"--swipe-amount\", `${distance}px`);\n\n// Good: only affects this element\nelement.style.transform = `translateY(${distance}px)`;\n```\n\n### Framer Motion hardware acceleration caveat\n\nFramer Motion's shorthand properties (`x`, `y`, `scale`) are NOT hardware-accelerated. They use `requestAnimationFrame` on the main thread. For hardware acceleration, use the full `transform` string:\n\n```jsx\n// NOT hardware accelerated (convenient but drops frames under load)\n<motion.div animate={{ x: 100 }} />\n\n// Hardware accelerated (stays smooth even when main thread is busy)\n<motion.div animate={{ transform: \"translateX(100px)\" }} />\n```\n\nThis matters when the browser is simultaneously loading content, running scripts, or painting. At Vercel, the dashboard tab animation used Shared Layout Animations and dropped frames during page loads. Switching to CSS animations (off main thread) fixed it.\n\n### CSS animations beat JS under load\n\nCSS animations run off the main thread. When the browser is busy loading a new page, Framer Motion animations (using `requestAnimationFrame`) drop frames. CSS animations remain smooth. Use CSS for predetermined animations; JS for dynamic, interruptible ones.\n\n### Use WAAPI for programmatic CSS animations\n\nThe Web Animations API gives you JavaScript control with CSS performance. Hardware-accelerated, interruptible, and no library needed.\n\n```js\nelement.animate(\n  [{ clipPath: \"inset(0 0 100% 0)\" }, { clipPath: \"inset(0 0 0 0)\" }],\n  {\n    duration: 1000,\n    fill: \"forwards\",\n    easing: \"cubic-bezier(0.77, 0, 0.175, 1)\",\n  },\n);\n```\n\n## Accessibility\n\n### prefers-reduced-motion\n\nAnimations can cause motion sickness. Reduced motion means fewer and gentler animations, not zero. Keep opacity and color transitions that aid comprehension. Remove movement and position animations.\n\n```css\n@media (prefers-reduced-motion: reduce) {\n  .element {\n    animation: fade 0.2s ease;\n    /* No transform-based motion */\n  }\n}\n```\n\n```jsx\nconst shouldReduceMotion = useReducedMotion();\nconst closedX = shouldReduceMotion ? 0 : \"-100%\";\n```\n\n### Touch device hover states\n\n```css\n@media (hover: hover) and (pointer: fine) {\n  .element:hover {\n    transform: scale(1.05);\n  }\n}\n```\n\nTouch devices trigger hover on tap, causing false positives. Gate hover animations behind this media query.\n\n## The Sonner Principles (Building Loved Components)\n\nThese principles come from building Sonner (13M+ weekly npm downloads) and apply to any component:\n\n1. **Developer experience is key.** No hooks, no context, no complex setup. Insert `<Toaster />` once, call `toast()` from anywhere. The less friction to adopt, the more people will use it.\n\n2. **Good defaults matter more than options.** Ship beautiful out of the box. Most users never customize. The default easing, timing, and visual design should be excellent.\n\n3. **Naming creates identity.** \"Sonner\" (French for \"to ring\") feels more elegant than \"react-toast\". Sacrifice discoverability for memorability when appropriate.\n\n4. **Handle edge cases invisibly.** Pause toast timers when the tab is hidden. Fill gaps between stacked toasts with pseudo-elements to maintain hover state. Capture pointer events during drag. Users never notice these, and that is exactly right.\n\n5. **Use transitions, not keyframes, for dynamic UI.** Toasts are added rapidly. Keyframes restart from zero on interruption. Transitions retarget smoothly.\n\n6. **Build a great documentation site.** Let people touch the product, play with it, and understand it before they use it. Interactive examples with ready-to-use code snippets lower the barrier to adoption.\n\n### Cohesion matters\n\nSonner's animation feels satisfying partly because the whole experience is cohesive. The easing and duration fit the vibe of the library. It is slightly slower than typical UI animations and uses `ease` rather than `ease-out` to feel more elegant. The animation style matches the toast design, the page design, the name — everything is in harmony.\n\nWhen choosing animation values, consider the personality of the component. A playful component can be bouncier. A professional dashboard should be crisp and fast. Match the motion to the mood.\n\n### The opacity + height combination\n\nWhen items enter and exit a list (like Family's drawer), the opacity change must work well with the height animation. This is often trial and error. There is no formula — you adjust until it feels right.\n\n### Review your work the next day\n\nReview animations with fresh eyes. You notice imperfections the next day that you missed during development. Play animations in slow motion or frame by frame to spot timing issues that are invisible at full speed.\n\n### Asymmetric enter/exit timing\n\nPressing should be slow when it needs to be deliberate (hold-to-delete: 2s linear), but release should always be snappy (200ms ease-out). This pattern applies broadly: slow where the user is deciding, fast where the system is responding.\n\n```css\n/* Release: fast */\n.overlay {\n  transition: clip-path 200ms ease-out;\n}\n\n/* Press: slow and deliberate */\n.button:active .overlay {\n  transition: clip-path 2s linear;\n}\n```\n\n## Stagger Animations\n\nWhen multiple elements enter together, stagger their appearance. Each element animates in with a small delay after the previous one. This creates a cascading effect that feels more natural than everything appearing at once.\n\n```css\n.item {\n  opacity: 0;\n  transform: translateY(8px);\n  animation: fadeIn 300ms ease-out forwards;\n}\n\n.item:nth-child(1) {\n  animation-delay: 0ms;\n}\n.item:nth-child(2) {\n  animation-delay: 50ms;\n}\n.item:nth-child(3) {\n  animation-delay: 100ms;\n}\n.item:nth-child(4) {\n  animation-delay: 150ms;\n}\n\n@keyframes fadeIn {\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n```\n\nKeep stagger delays short (30-80ms between items). Long delays make the interface feel slow. Stagger is decorative — never block interaction while stagger animations are playing.\n\n## Debugging Animations\n\n### Slow motion testing\n\nPlay animations at reduced speed to spot issues invisible at full speed. Temporarily increase duration to 2-5x normal, or use browser DevTools animation inspector to slow playback.\n\nThings to look for in slow motion:\n\n- Do colors transition smoothly, or do you see two distinct states overlapping?\n- Does the easing feel right, or does it start/stop abruptly?\n- Is the transform-origin correct, or does the element scale from the wrong point?\n- Are multiple animated properties (opacity, transform, color) in sync?\n\n### Frame-by-frame inspection\n\nStep through animations frame by frame in Chrome DevTools (Animations panel). This reveals timing issues between coordinated properties that you cannot see at full speed.\n\n### Test on real devices\n\nFor touch interactions (drawers, swipe gestures), test on physical devices. Connect your phone via USB, visit your local dev server by IP address, and use Safari's remote devtools. The Xcode Simulator is an alternative but real hardware is better for gesture testing.\n\n## Review Checklist\n\nWhen reviewing UI code, check for:\n\n| Issue                                  | Fix                                                                                           |\n| -------------------------------------- | --------------------------------------------------------------------------------------------- |\n| `transition: all`                      | Specify exact properties: `transition: transform 200ms ease-out`                              |\n| `scale(0)` entry animation             | Start from `scale(0.95)` with `opacity: 0`                                                    |\n| `ease-in` on UI element                | Switch to `ease-out` or custom curve                                                          |\n| `transform-origin: center` on popover  | Set to trigger location or use Radix/Base UI CSS variable (modals are exempt — keep centered) |\n| Animation on keyboard action           | Remove animation entirely                                                                     |\n| Duration > 300ms on UI element         | Reduce to 150-250ms                                                                           |\n| Hover animation without media query    | Add `@media (hover: hover) and (pointer: fine)`                                               |\n| Keyframes on rapidly-triggered element | Use CSS transitions for interruptibility                                                      |\n| Framer Motion `x`/`y` props under load | Use `transform: \"translateX()\"` for hardware acceleration                                     |\n| Same enter/exit transition speed       | Make exit faster than enter (e.g., enter 2s, exit 200ms)                                      |\n| Elements all appear at once            | Add stagger delay (30-80ms between items)                                                     |\n",".agents/skills/frontend-design/SKILL.md":"---\nname: frontend-design\ndescription: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.\n---\n\nThis skill guides creation of distinctive, production-grade frontend interfaces that avoid generic \"AI slop\" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.\n\nThe user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.\n\n## Design Thinking\n\nBefore coding, understand the context and commit to a BOLD aesthetic direction:\n- **Purpose**: What problem does this interface solve? Who uses it?\n- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.\n- **Constraints**: Technical requirements (framework, performance, accessibility).\n- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?\n\n**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.\n\nThen implement working code (HTML/CSS/JS, React, Vue, etc.) that is:\n- Production-grade and functional\n- Visually striking and memorable\n- Cohesive with a clear aesthetic point-of-view\n- Meticulously refined in every detail\n\n## Frontend Aesthetics Guidelines\n\nFocus on:\n- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.\n- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.\n- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.\n- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.\n- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.\n\nNEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.\n\nInterpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.\n\n**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.\n\nRemember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.\n",".agents/skills/plan-mode/SKILL.md":"---\nname: plan-mode\ndescription: Holistic, system-aware planning before implementing non-trivial tasks. Use when the task involves new features, architectural decisions, multi-file changes, unclear requirements, or multiple valid approaches. Triggers on \"/plan\", \"plan this\", \"design an approach\", \"let's plan first\".\n---\n\nFor non-trivial implementation tasks, plan before you code. Treat every change as a system change, not an isolated patch. Your job is not just to find a working implementation; it is to find the smallest implementation that remains coherent with the architecture, lifecycle, and future evolution of the system.\n\n## Planning Doctrine\n\n- **Think broadly, implement narrowly.** Analyze the surrounding system before deciding where the change belongs.\n- **Every change has system implications.** Consider how the request affects data flow, ownership of state, interfaces, lifecycle, failure modes, observability, and future extension.\n- **Prefer authoritative, derivable designs.** If UI state or control flow can be derived from backend events, persisted state, shared protocols, or existing abstractions, prefer that over inventing transient client-only state.\n- **Choose the smallest coherent solution.** Do not default to the smallest local patch if it distorts the architecture or adds hidden follow-on complexity.\n- **Expand scope only when it simplifies the system.** Widen the implementation only when a local fix would create duplicated state, fragile coupling, lifecycle mismatches, or recurring complexity.\n- **Do not gold-plate.** Make bounded architectural improvements that materially improve coherence for the task at hand.\n\n## Plan File\n\nWrite your plan to a `PLAN.md` file in the project root. Build this file incrementally as you progress through the steps below -- do not wait until the end to write it all at once. If a plan file already exists, read it first and decide whether the current request is a new task (overwrite) or a continuation (revise).\n\n## Step 1: Explore\n\nThoroughly explore the codebase to understand the request before designing anything.\n\n- Read the relevant files and understand existing patterns, architecture, and conventions.\n- Trace the end-to-end flow, not just the local implementation point.\n- Identify the current **source of truth**, state ownership, boundaries between subsystems, and any existing invariants.\n- Identify the full lifecycle of the behavior: trigger, processing, intermediate states, completion, side effects, failure, retry, and cleanup.\n- Search for similar features and prior art in the codebase.\n- **Launch parallel explorations** when the scope is uncertain or multiple areas of the codebase are involved. Give each exploration a specific, distinct search focus (e.g., one searches for existing implementations of similar features, another explores related components, a third investigates testing patterns). Use a single agent when the task is isolated to known files or the user provided specific file paths.\n- Do not start implementing yet.\n\n## Step 2: Clarify\n\nAsk the user questions to resolve ambiguities before committing to an approach. This may cover technical implementation, desired system behavior, UI/UX, performance, edge cases, ownership of state, failure handling, or tradeoffs. You may ask multiple rounds, reading more code in between. Do not make large assumptions about user intent.\n\n## Step 3: Design\n\nBased on exploration and user input, design a concrete implementation approach:\n\n- Provide comprehensive background context including filenames, code path traces, and the surrounding system behavior discovered in Step 1.\n- Treat the request as a system modification, not just a local diff.\n- Explicitly consider both:\n  - **The most direct implementation**\n  - **The most system-coherent implementation**\n- Choose the direct implementation only if it does **not** introduce architectural distortion, duplicated state, fragile coupling, lifecycle mismatches, or hidden follow-on complexity.\n- Prefer the smallest holistic solution: think broadly about implications, then keep the implementation as narrow as possible while preserving coherence.\n- For complex tasks, consider multiple perspectives to arrive at the best approach:\n  - **New feature**: simplicity vs performance vs maintainability\n  - **Bug fix**: root cause fix vs workaround vs prevention\n  - **Refactoring**: minimal change vs clean architecture\n- For tasks that touch multiple parts of the codebase, involve large refactors, or have many edge cases, explore different approaches in parallel before converging on a recommendation.\n\nBefore finalizing the design, explicitly answer these questions:\n\n1. What part of the system is actually changing?\n2. What is the source of truth before and after this change?\n3. What new state, transitions, or invariants does this introduce?\n4. What other components, flows, or interfaces now depend on this decision?\n5. Does this create duplicated logic or state anywhere?\n6. Is there a more system-coherent place to implement this?\n7. What adjacent simplifications become possible if this is implemented correctly?\n8. What is the smallest solution that keeps the system coherent?\n\n## Step 4: Review\n\nBefore finalizing, review your design against the original request:\n\n1. Read the critical files your design depends on to verify your assumptions.\n2. Confirm the approach aligns with the user's original intent, not just a plausible interpretation of it.\n3. Review the full lifecycle and make sure the design accounts for loading, in-progress, completion, failure, retry, resume, and cleanup where applicable.\n4. Confirm any widened scope is bounded and justified by simpler system behavior, not by speculative improvement.\n5. Ask remaining clarifying questions if anything is still ambiguous.\n\n## Step 5: Present the Plan\n\nWrite the final plan to the plan file. The plan should be:\n\n- **Concise enough to scan quickly, detailed enough to execute.** Include only your recommended approach, not all alternatives.\n- **Specific about files.** List the paths of critical files to be modified and what changes in each.\n- **Explicit about system impact.** Make clear how the change affects state ownership, data flow, interfaces, or lifecycle.\n- **Verifiable.** Include how to test the changes end-to-end.\n\nStructure:\n\n```\nSummary: 1-2 sentences on the task and chosen approach\n\nContext: Key findings from exploration -- existing patterns, relevant files, constraints\n\nSystem Impact: How the change affects source of truth, data flow, lifecycle, and dependent parts of the system\n\nApproach: High-level design decision and why\n\nChanges:\n- `path/to/file.ts` - what changes and why\n- `path/to/other.ts` - what changes and why\n\nVerification:\n- How to test end-to-end\n- Relevant test commands\n- Edge cases to check\n```\n\nPresent the plan to the user and wait for approval. If the user has feedback, revise accordingly.\n\n## Step 6: Implement and Verify\n\nOnce approved:\n\n1. Implement the plan, tracking progress against each item.\n2. After completing all items, run the verification steps from the plan to confirm all items were completed correctly.\n",".agents/skills/remove-demo-limits/SKILL.md":"---\nname: remove-demo-limits\ndescription: Removes Open Harness hosted demo restrictions from a fork. Use when a maintainer wants to remove managed-template trial caps, hosted deployment gating, or \"deploy your own\" limits. Triggers on \"remove demo limits\", \"remove trial limits\", \"remove hosted restrictions\", \"open this up for my fork\", \"remove managed template restrictions\".\n---\n\nRemove the managed-template trial restrictions with the smallest coherent change.\n\n## Goal\nMake the fork behave like a normal self-hosted deployment:\n- no trial session cap\n- no trial message cap\n- no hosted-only restriction errors\n- no hosted-demo delete restrictions\n- no dead helper code or stale tests\n\n## Inspect first\nRead these files before editing:\n- `apps/web/lib/managed-template-trial.ts`\n- `apps/web/app/api/sessions/route.ts`\n- `apps/web/app/api/chat/route.ts`\n- `apps/web/app/api/sessions/[sessionId]/chats/[chatId]/messages/[messageId]/route.ts`\n- `apps/web/app/api/sessions/route.test.ts`\n- `apps/web/app/api/chat/route.test.ts`\n- `apps/web/app/api/sessions/[sessionId]/chats/[chatId]/messages/[messageId]/route.test.ts`\n\nThen search for these identifiers and strings to catch older or newer variants:\n- `isManagedTemplateTrialUser`\n- `MANAGED_TEMPLATE_TRIAL_`\n- `deploy-your-own`\n- `open-agents.dev`\n- `vercel.com`\n- `This hosted deployment includes`\n- `does not allow message deletion`\n\n## Required changes\n1. Remove the guards that block session creation, chat messages, or message deletion for managed-template users.\n2. If the auth callback still blocks non-Vercel emails on hosted deployments, remove that restriction too.\n3. Delete `apps/web/lib/managed-template-trial.ts` if it becomes unused; otherwise simplify it so no dead exports remain.\n4. Remove or update tests that assert the hosted caps, delete restrictions, or hosted-only auth gating.\n5. Do not add feature flags, env toggles, or new dependencies. Prefer deleting the restriction code outright.\n6. Keep unrelated auth, session ownership, and sandbox behavior unchanged.\n\n## Verification\nThis repo uses Bun. After the edits:\n- run `bun run ci`\n- if a failure is pre-existing, say that clearly and separate it from your changes\n\n## Output\nReport:\n- which restrictions were removed\n- which files changed\n- verification result\n",".agents/skills/vercel-react-best-practices/AGENTS.md":"# React Best Practices\n\n**Version 1.0.0**  \nVercel Engineering  \nJanuary 2026\n\n> **Note:**  \n> This document is mainly for agents and LLMs to follow when maintaining,  \n> generating, or refactoring React and Next.js codebases. Humans  \n> may also find it useful, but guidance here is optimized for automation  \n> and consistency by AI-assisted workflows.\n\n---\n\n## Abstract\n\nComprehensive 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.\n\n---\n\n## Table of Contents\n\n1. [Eliminating Waterfalls](#1-eliminating-waterfalls) — **CRITICAL**\n   - 1.1 [Defer Await Until Needed](#11-defer-await-until-needed)\n   - 1.2 [Dependency-Based Parallelization](#12-dependency-based-parallelization)\n   - 1.3 [Prevent Waterfall Chains in API Routes](#13-prevent-waterfall-chains-in-api-routes)\n   - 1.4 [Promise.all() for Independent Operations](#14-promiseall-for-independent-operations)\n   - 1.5 [Strategic Suspense Boundaries](#15-strategic-suspense-boundaries)\n2. [Bundle Size Optimization](#2-bundle-size-optimization) — **CRITICAL**\n   - 2.1 [Avoid Barrel File Imports](#21-avoid-barrel-file-imports)\n   - 2.2 [Conditional Module Loading](#22-conditional-module-loading)\n   - 2.3 [Defer Non-Critical Third-Party Libraries](#23-defer-non-critical-third-party-libraries)\n   - 2.4 [Dynamic Imports for Heavy Components](#24-dynamic-imports-for-heavy-components)\n   - 2.5 [Preload Based on User Intent](#25-preload-based-on-user-intent)\n3. [Server-Side Performance](#3-server-side-performance) — **HIGH**\n   - 3.1 [Authenticate Server Actions Like API Routes](#31-authenticate-server-actions-like-api-routes)\n   - 3.2 [Avoid Duplicate Serialization in RSC Props](#32-avoid-duplicate-serialization-in-rsc-props)\n   - 3.3 [Cross-Request LRU Caching](#33-cross-request-lru-caching)\n   - 3.4 [Hoist Static I/O to Module Level](#34-hoist-static-io-to-module-level)\n   - 3.5 [Minimize Serialization at RSC Boundaries](#35-minimize-serialization-at-rsc-boundaries)\n   - 3.6 [Parallel Data Fetching with Component Composition](#36-parallel-data-fetching-with-component-composition)\n   - 3.7 [Per-Request Deduplication with React.cache()](#37-per-request-deduplication-with-reactcache)\n   - 3.8 [Use after() for Non-Blocking Operations](#38-use-after-for-non-blocking-operations)\n4. [Client-Side Data Fetching](#4-client-side-data-fetching) — **MEDIUM-HIGH**\n   - 4.1 [Deduplicate Global Event Listeners](#41-deduplicate-global-event-listeners)\n   - 4.2 [Use Passive Event Listeners for Scrolling Performance](#42-use-passive-event-listeners-for-scrolling-performance)\n   - 4.3 [Use SWR for Automatic Deduplication](#43-use-swr-for-automatic-deduplication)\n   - 4.4 [Version and Minimize localStorage Data](#44-version-and-minimize-localstorage-data)\n5. [Re-render Optimization](#5-re-render-optimization) — **MEDIUM**\n   - 5.1 [Calculate Derived State During Rendering](#51-calculate-derived-state-during-rendering)\n   - 5.2 [Defer State Reads to Usage Point](#52-defer-state-reads-to-usage-point)\n   - 5.3 [Do not wrap a simple expression with a primitive result type in useMemo](#53-do-not-wrap-a-simple-expression-with-a-primitive-result-type-in-usememo)\n   - 5.4 [Extract Default Non-primitive Parameter Value from Memoized Component to Constant](#54-extract-default-non-primitive-parameter-value-from-memoized-component-to-constant)\n   - 5.5 [Extract to Memoized Components](#55-extract-to-memoized-components)\n   - 5.6 [Narrow Effect Dependencies](#56-narrow-effect-dependencies)\n   - 5.7 [Put Interaction Logic in Event Handlers](#57-put-interaction-logic-in-event-handlers)\n   - 5.8 [Subscribe to Derived State](#58-subscribe-to-derived-state)\n   - 5.9 [Use Functional setState Updates](#59-use-functional-setstate-updates)\n   - 5.10 [Use Lazy State Initialization](#510-use-lazy-state-initialization)\n   - 5.11 [Use Transitions for Non-Urgent Updates](#511-use-transitions-for-non-urgent-updates)\n   - 5.12 [Use useRef for Transient Values](#512-use-useref-for-transient-values)\n6. [Rendering Performance](#6-rendering-performance) — **MEDIUM**\n   - 6.1 [Animate SVG Wrapper Instead of SVG Element](#61-animate-svg-wrapper-instead-of-svg-element)\n   - 6.2 [CSS content-visibility for Long Lists](#62-css-content-visibility-for-long-lists)\n   - 6.3 [Hoist Static JSX Elements](#63-hoist-static-jsx-elements)\n   - 6.4 [Optimize SVG Precision](#64-optimize-svg-precision)\n   - 6.5 [Prevent Hydration Mismatch Without Flickering](#65-prevent-hydration-mismatch-without-flickering)\n   - 6.6 [Suppress Expected Hydration Mismatches](#66-suppress-expected-hydration-mismatches)\n   - 6.7 [Use Activity Component for Show/Hide](#67-use-activity-component-for-showhide)\n   - 6.8 [Use Explicit Conditional Rendering](#68-use-explicit-conditional-rendering)\n   - 6.9 [Use useTransition Over Manual Loading States](#69-use-usetransition-over-manual-loading-states)\n7. [JavaScript Performance](#7-javascript-performance) — **LOW-MEDIUM**\n   - 7.1 [Avoid Layout Thrashing](#71-avoid-layout-thrashing)\n   - 7.2 [Build Index Maps for Repeated Lookups](#72-build-index-maps-for-repeated-lookups)\n   - 7.3 [Cache Property Access in Loops](#73-cache-property-access-in-loops)\n   - 7.4 [Cache Repeated Function Calls](#74-cache-repeated-function-calls)\n   - 7.5 [Cache Storage API Calls](#75-cache-storage-api-calls)\n   - 7.6 [Combine Multiple Array Iterations](#76-combine-multiple-array-iterations)\n   - 7.7 [Early Length Check for Array Comparisons](#77-early-length-check-for-array-comparisons)\n   - 7.8 [Early Return from Functions](#78-early-return-from-functions)\n   - 7.9 [Hoist RegExp Creation](#79-hoist-regexp-creation)\n   - 7.10 [Use Loop for Min/Max Instead of Sort](#710-use-loop-for-minmax-instead-of-sort)\n   - 7.11 [Use Set/Map for O(1) Lookups](#711-use-setmap-for-o1-lookups)\n   - 7.12 [Use toSorted() Instead of sort() for Immutability](#712-use-tosorted-instead-of-sort-for-immutability)\n8. [Advanced Patterns](#8-advanced-patterns) — **LOW**\n   - 8.1 [Initialize App Once, Not Per Mount](#81-initialize-app-once-not-per-mount)\n   - 8.2 [Store Event Handlers in Refs](#82-store-event-handlers-in-refs)\n   - 8.3 [useEffectEvent for Stable Callback Refs](#83-useeffectevent-for-stable-callback-refs)\n\n---\n\n## 1. Eliminating Waterfalls\n\n**Impact: CRITICAL**\n\nWaterfalls are the #1 performance killer. Each sequential await adds full network latency. Eliminating them yields the largest gains.\n\n### 1.1 Defer Await Until Needed\n\n**Impact: HIGH (avoids blocking unused code paths)**\n\nMove `await` operations into the branches where they're actually used to avoid blocking code paths that don't need them.\n\n**Incorrect: blocks both branches**\n\n```typescript\nasync function handleRequest(userId: string, skipProcessing: boolean) {\n  const userData = await fetchUserData(userId)\n  \n  if (skipProcessing) {\n    // Returns immediately but still waited for userData\n    return { skipped: true }\n  }\n  \n  // Only this branch uses userData\n  return processUserData(userData)\n}\n```\n\n**Correct: only blocks when needed**\n\n```typescript\nasync function handleRequest(userId: string, skipProcessing: boolean) {\n  if (skipProcessing) {\n    // Returns immediately without waiting\n    return { skipped: true }\n  }\n  \n  // Fetch only when needed\n  const userData = await fetchUserData(userId)\n  return processUserData(userData)\n}\n```\n\n**Another example: early return optimization**\n\n```typescript\n// Incorrect: always fetches permissions\nasync function updateResource(resourceId: string, userId: string) {\n  const permissions = await fetchPermissions(userId)\n  const resource = await getResource(resourceId)\n  \n  if (!resource) {\n    return { error: 'Not found' }\n  }\n  \n  if (!permissions.canEdit) {\n    return { error: 'Forbidden' }\n  }\n  \n  return await updateResourceData(resource, permissions)\n}\n\n// Correct: fetches only when needed\nasync function updateResource(resourceId: string, userId: string) {\n  const resource = await getResource(resourceId)\n  \n  if (!resource) {\n    return { error: 'Not found' }\n  }\n  \n  const permissions = await fetchPermissions(userId)\n  \n  if (!permissions.canEdit) {\n    return { error: 'Forbidden' }\n  }\n  \n  return await updateResourceData(resource, permissions)\n}\n```\n\nThis optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive.\n\n### 1.2 Dependency-Based Parallelization\n\n**Impact: CRITICAL (2-10× improvement)**\n\nFor operations with partial dependencies, use `better-all` to maximize parallelism. It automatically starts each task at the earliest possible moment.\n\n**Incorrect: profile waits for config unnecessarily**\n\n```typescript\nconst [user, config] = await Promise.all([\n  fetchUser(),\n  fetchConfig()\n])\nconst profile = await fetchProfile(user.id)\n```\n\n**Correct: config and profile run in parallel**\n\n```typescript\nimport { all } from 'better-all'\n\nconst { user, config, profile } = await all({\n  async user() { return fetchUser() },\n  async config() { return fetchConfig() },\n  async profile() {\n    return fetchProfile((await this.$.user).id)\n  }\n})\n```\n\n**Alternative without extra dependencies:**\n\n```typescript\nconst userPromise = fetchUser()\nconst profilePromise = userPromise.then(user => fetchProfile(user.id))\n\nconst [user, config, profile] = await Promise.all([\n  userPromise,\n  fetchConfig(),\n  profilePromise\n])\n```\n\nWe can also create all the promises first, and do `Promise.all()` at the end.\n\nReference: [https://github.com/shuding/better-all](https://github.com/shuding/better-all)\n\n### 1.3 Prevent Waterfall Chains in API Routes\n\n**Impact: CRITICAL (2-10× improvement)**\n\nIn API routes and Server Actions, start independent operations immediately, even if you don't await them yet.\n\n**Incorrect: config waits for auth, data waits for both**\n\n```typescript\nexport async function GET(request: Request) {\n  const session = await auth()\n  const config = await fetchConfig()\n  const data = await fetchData(session.user.id)\n  return Response.json({ data, config })\n}\n```\n\n**Correct: auth and config start immediately**\n\n```typescript\nexport async function GET(request: Request) {\n  const sessionPromise = auth()\n  const configPromise = fetchConfig()\n  const session = await sessionPromise\n  const [config, data] = await Promise.all([\n    configPromise,\n    fetchData(session.user.id)\n  ])\n  return Response.json({ data, config })\n}\n```\n\nFor operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization).\n\n### 1.4 Promise.all() for Independent Operations\n\n**Impact: CRITICAL (2-10× improvement)**\n\nWhen async operations have no interdependencies, execute them concurrently using `Promise.all()`.\n\n**Incorrect: sequential execution, 3 round trips**\n\n```typescript\nconst user = await fetchUser()\nconst posts = await fetchPosts()\nconst comments = await fetchComments()\n```\n\n**Correct: parallel execution, 1 round trip**\n\n```typescript\nconst [user, posts, comments] = await Promise.all([\n  fetchUser(),\n  fetchPosts(),\n  fetchComments()\n])\n```\n\n### 1.5 Strategic Suspense Boundaries\n\n**Impact: HIGH (faster initial paint)**\n\nInstead of awaiting data in async components before returning JSX, use Suspense boundaries to show the wrapper UI faster while data loads.\n\n**Incorrect: wrapper blocked by data fetching**\n\n```tsx\nasync function Page() {\n  const data = await fetchData() // Blocks entire page\n  \n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <div>\n        <DataDisplay data={data} />\n      </div>\n      <div>Footer</div>\n    </div>\n  )\n}\n```\n\nThe entire layout waits for data even though only the middle section needs it.\n\n**Correct: wrapper shows immediately, data streams in**\n\n```tsx\nfunction Page() {\n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <div>\n        <Suspense fallback={<Skeleton />}>\n          <DataDisplay />\n        </Suspense>\n      </div>\n      <div>Footer</div>\n    </div>\n  )\n}\n\nasync function DataDisplay() {\n  const data = await fetchData() // Only blocks this component\n  return <div>{data.content}</div>\n}\n```\n\nSidebar, Header, and Footer render immediately. Only DataDisplay waits for data.\n\n**Alternative: share promise across components**\n\n```tsx\nfunction Page() {\n  // Start fetch immediately, but don't await\n  const dataPromise = fetchData()\n  \n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <Suspense fallback={<Skeleton />}>\n        <DataDisplay dataPromise={dataPromise} />\n        <DataSummary dataPromise={dataPromise} />\n      </Suspense>\n      <div>Footer</div>\n    </div>\n  )\n}\n\nfunction DataDisplay({ dataPromise }: { dataPromise: Promise<Data> }) {\n  const data = use(dataPromise) // Unwraps the promise\n  return <div>{data.content}</div>\n}\n\nfunction DataSummary({ dataPromise }: { dataPromise: Promise<Data> }) {\n  const data = use(dataPromise) // Reuses the same promise\n  return <div>{data.summary}</div>\n}\n```\n\nBoth components share the same promise, so only one fetch occurs. Layout renders immediately while both components wait together.\n\n**When NOT to use this pattern:**\n\n- Critical data needed for layout decisions (affects positioning)\n\n- SEO-critical content above the fold\n\n- Small, fast queries where suspense overhead isn't worth it\n\n- When you want to avoid layout shift (loading → content jump)\n\n**Trade-off:** Faster initial paint vs potential layout shift. Choose based on your UX priorities.\n\n---\n\n## 2. Bundle Size Optimization\n\n**Impact: CRITICAL**\n\nReducing initial bundle size improves Time to Interactive and Largest Contentful Paint.\n\n### 2.1 Avoid Barrel File Imports\n\n**Impact: CRITICAL (200-800ms import cost, slow builds)**\n\nImport directly from source files instead of barrel files to avoid loading thousands of unused modules. **Barrel files** are entry points that re-export multiple modules (e.g., `index.js` that does `export * from './module'`).\n\nPopular icon and component libraries can have **up to 10,000 re-exports** in their entry file. For many React packages, **it takes 200-800ms just to import them**, affecting both development speed and production cold starts.\n\n**Why tree-shaking doesn't help:** When a library is marked as external (not bundled), the bundler can't optimize it. If you bundle it to enable tree-shaking, builds become substantially slower analyzing the entire module graph.\n\n**Incorrect: imports entire library**\n\n```tsx\nimport { Check, X, Menu } from 'lucide-react'\n// Loads 1,583 modules, takes ~2.8s extra in dev\n// Runtime cost: 200-800ms on every cold start\n\nimport { Button, TextField } from '@mui/material'\n// Loads 2,225 modules, takes ~4.2s extra in dev\n```\n\n**Correct: imports only what you need**\n\n```tsx\nimport Check from 'lucide-react/dist/esm/icons/check'\nimport X from 'lucide-react/dist/esm/icons/x'\nimport Menu from 'lucide-react/dist/esm/icons/menu'\n// Loads only 3 modules (~2KB vs ~1MB)\n\nimport Button from '@mui/material/Button'\nimport TextField from '@mui/material/TextField'\n// Loads only what you use\n```\n\n**Alternative: Next.js 13.5+**\n\n```js\n// next.config.js - use optimizePackageImports\nmodule.exports = {\n  experimental: {\n    optimizePackageImports: ['lucide-react', '@mui/material']\n  }\n}\n\n// Then you can keep the ergonomic barrel imports:\nimport { Check, X, Menu } from 'lucide-react'\n// Automatically transformed to direct imports at build time\n```\n\nDirect imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR.\n\nLibraries commonly affected: `lucide-react`, `@mui/material`, `@mui/icons-material`, `@tabler/icons-react`, `react-icons`, `@headlessui/react`, `@radix-ui/react-*`, `lodash`, `ramda`, `date-fns`, `rxjs`, `react-use`.\n\nReference: [https://vercel.com/blog/how-we-optimized-package-imports-in-next-js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)\n\n### 2.2 Conditional Module Loading\n\n**Impact: HIGH (loads large data only when needed)**\n\nLoad large data or modules only when a feature is activated.\n\n**Example: lazy-load animation frames**\n\n```tsx\nfunction AnimationPlayer({ enabled, setEnabled }: { enabled: boolean; setEnabled: React.Dispatch<React.SetStateAction<boolean>> }) {\n  const [frames, setFrames] = useState<Frame[] | null>(null)\n\n  useEffect(() => {\n    if (enabled && !frames && typeof window !== 'undefined') {\n      import('./animation-frames.js')\n        .then(mod => setFrames(mod.frames))\n        .catch(() => setEnabled(false))\n    }\n  }, [enabled, frames, setEnabled])\n\n  if (!frames) return <Skeleton />\n  return <Canvas frames={frames} />\n}\n```\n\nThe `typeof window !== 'undefined'` check prevents bundling this module for SSR, optimizing server bundle size and build speed.\n\n### 2.3 Defer Non-Critical Third-Party Libraries\n\n**Impact: MEDIUM (loads after hydration)**\n\nAnalytics, logging, and error tracking don't block user interaction. Load them after hydration.\n\n**Incorrect: blocks initial bundle**\n\n```tsx\nimport { Analytics } from '@vercel/analytics/react'\n\nexport default function RootLayout({ children }) {\n  return (\n    <html>\n      <body>\n        {children}\n        <Analytics />\n      </body>\n    </html>\n  )\n}\n```\n\n**Correct: loads after hydration**\n\n```tsx\nimport dynamic from 'next/dynamic'\n\nconst Analytics = dynamic(\n  () => import('@vercel/analytics/react').then(m => m.Analytics),\n  { ssr: false }\n)\n\nexport default function RootLayout({ children }) {\n  return (\n    <html>\n      <body>\n        {children}\n        <Analytics />\n      </body>\n    </html>\n  )\n}\n```\n\n### 2.4 Dynamic Imports for Heavy Components\n\n**Impact: CRITICAL (directly affects TTI and LCP)**\n\nUse `next/dynamic` to lazy-load large components not needed on initial render.\n\n**Incorrect: Monaco bundles with main chunk ~300KB**\n\n```tsx\nimport { MonacoEditor } from './monaco-editor'\n\nfunction CodePanel({ code }: { code: string }) {\n  return <MonacoEditor value={code} />\n}\n```\n\n**Correct: Monaco loads on demand**\n\n```tsx\nimport dynamic from 'next/dynamic'\n\nconst MonacoEditor = dynamic(\n  () => import('./monaco-editor').then(m => m.MonacoEditor),\n  { ssr: false }\n)\n\nfunction CodePanel({ code }: { code: string }) {\n  return <MonacoEditor value={code} />\n}\n```\n\n### 2.5 Preload Based on User Intent\n\n**Impact: MEDIUM (reduces perceived latency)**\n\nPreload heavy bundles before they're needed to reduce perceived latency.\n\n**Example: preload on hover/focus**\n\n```tsx\nfunction EditorButton({ onClick }: { onClick: () => void }) {\n  const preload = () => {\n    if (typeof window !== 'undefined') {\n      void import('./monaco-editor')\n    }\n  }\n\n  return (\n    <button\n      onMouseEnter={preload}\n      onFocus={preload}\n      onClick={onClick}\n    >\n      Open Editor\n    </button>\n  )\n}\n```\n\n**Example: preload when feature flag is enabled**\n\n```tsx\nfunction FlagsProvider({ children, flags }: Props) {\n  useEffect(() => {\n    if (flags.editorEnabled && typeof window !== 'undefined') {\n      void import('./monaco-editor').then(mod => mod.init())\n    }\n  }, [flags.editorEnabled])\n\n  return <FlagsContext.Provider value={flags}>\n    {children}\n  </FlagsContext.Provider>\n}\n```\n\nThe `typeof window !== 'undefined'` check prevents bundling preloaded modules for SSR, optimizing server bundle size and build speed.\n\n---\n\n## 3. Server-Side Performance\n\n**Impact: HIGH**\n\nOptimizing server-side rendering and data fetching eliminates server-side waterfalls and reduces response times.\n\n### 3.1 Authenticate Server Actions Like API Routes\n\n**Impact: CRITICAL (prevents unauthorized access to server mutations)**\n\nServer Actions (functions with `\"use server\"`) are exposed as public endpoints, just like API routes. Always verify authentication and authorization **inside** each Server Action—do not rely solely on middleware, layout guards, or page-level checks, as Server Actions can be invoked directly.\n\nNext.js documentation explicitly states: \"Treat Server Actions with the same security considerations as public-facing API endpoints, and verify if the user is allowed to perform a mutation.\"\n\n**Incorrect: no authentication check**\n\n```typescript\n'use server'\n\nexport async function deleteUser(userId: string) {\n  // Anyone can call this! No auth check\n  await db.user.delete({ where: { id: userId } })\n  return { success: true }\n}\n```\n\n**Correct: authentication inside the action**\n\n```typescript\n'use server'\n\nimport { verifySession } from '@/lib/auth'\nimport { unauthorized } from '@/lib/errors'\n\nexport async function deleteUser(userId: string) {\n  // Always check auth inside the action\n  const session = await verifySession()\n  \n  if (!session) {\n    throw unauthorized('Must be logged in')\n  }\n  \n  // Check authorization too\n  if (session.user.role !== 'admin' && session.user.id !== userId) {\n    throw unauthorized('Cannot delete other users')\n  }\n  \n  await db.user.delete({ where: { id: userId } })\n  return { success: true }\n}\n```\n\n**With input validation:**\n\n```typescript\n'use server'\n\nimport { verifySession } from '@/lib/auth'\nimport { z } from 'zod'\n\nconst updateProfileSchema = z.object({\n  userId: z.string().uuid(),\n  name: z.string().min(1).max(100),\n  email: z.string().email()\n})\n\nexport async function updateProfile(data: unknown) {\n  // Validate input first\n  const validated = updateProfileSchema.parse(data)\n  \n  // Then authenticate\n  const session = await verifySession()\n  if (!session) {\n    throw new Error('Unauthorized')\n  }\n  \n  // Then authorize\n  if (session.user.id !== validated.userId) {\n    throw new Error('Can only update own profile')\n  }\n  \n  // Finally perform the mutation\n  await db.user.update({\n    where: { id: validated.userId },\n    data: {\n      name: validated.name,\n      email: validated.email\n    }\n  })\n  \n  return { success: true }\n}\n```\n\nReference: [https://nextjs.org/docs/app/guides/authentication](https://nextjs.org/docs/app/guides/authentication)\n\n### 3.2 Avoid Duplicate Serialization in RSC Props\n\n**Impact: LOW (reduces network payload by avoiding duplicate serialization)**\n\nRSC→client serialization deduplicates by object reference, not value. Same reference = serialized once; new reference = serialized again. Do transformations (`.toSorted()`, `.filter()`, `.map()`) in client, not server.\n\n**Incorrect: duplicates array**\n\n```tsx\n// RSC: sends 6 strings (2 arrays × 3 items)\n<ClientList usernames={usernames} usernamesOrdered={usernames.toSorted()} />\n```\n\n**Correct: sends 3 strings**\n\n```tsx\n// RSC: send once\n<ClientList usernames={usernames} />\n\n// Client: transform there\n'use client'\nconst sorted = useMemo(() => [...usernames].sort(), [usernames])\n```\n\n**Nested deduplication behavior:**\n\n```tsx\n// string[] - duplicates everything\nusernames={['a','b']} sorted={usernames.toSorted()} // sends 4 strings\n\n// object[] - duplicates array structure only\nusers={[{id:1},{id:2}]} sorted={users.toSorted()} // sends 2 arrays + 2 unique objects (not 4)\n```\n\nDeduplication works recursively. Impact varies by data type:\n\n- `string[]`, `number[]`, `boolean[]`: **HIGH impact** - array + all primitives fully duplicated\n\n- `object[]`: **LOW impact** - array duplicated, but nested objects deduplicated by reference\n\n**Operations breaking deduplication: create new references**\n\n- Arrays: `.toSorted()`, `.filter()`, `.map()`, `.slice()`, `[...arr]`\n\n- Objects: `{...obj}`, `Object.assign()`, `structuredClone()`, `JSON.parse(JSON.stringify())`\n\n**More examples:**\n\n```tsx\n// ❌ Bad\n<C users={users} active={users.filter(u => u.active)} />\n<C product={product} productName={product.name} />\n\n// ✅ Good\n<C users={users} />\n<C product={product} />\n// Do filtering/destructuring in client\n```\n\n**Exception:** Pass derived data when transformation is expensive or client doesn't need original.\n\n### 3.3 Cross-Request LRU Caching\n\n**Impact: HIGH (caches across requests)**\n\n`React.cache()` only works within one request. For data shared across sequential requests (user clicks button A then button B), use an LRU cache.\n\n**Implementation:**\n\n```typescript\nimport { LRUCache } from 'lru-cache'\n\nconst cache = new LRUCache<string, any>({\n  max: 1000,\n  ttl: 5 * 60 * 1000  // 5 minutes\n})\n\nexport async function getUser(id: string) {\n  const cached = cache.get(id)\n  if (cached) return cached\n\n  const user = await db.user.findUnique({ where: { id } })\n  cache.set(id, user)\n  return user\n}\n\n// Request 1: DB query, result cached\n// Request 2: cache hit, no DB query\n```\n\nUse when sequential user actions hit multiple endpoints needing the same data within seconds.\n\n**With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute):** LRU caching is especially effective because multiple concurrent requests can share the same function instance and cache. This means the cache persists across requests without needing external storage like Redis.\n\n**In traditional serverless:** Each invocation runs in isolation, so consider Redis for cross-process caching.\n\nReference: [https://github.com/isaacs/node-lru-cache](https://github.com/isaacs/node-lru-cache)\n\n### 3.4 Hoist Static I/O to Module Level\n\n**Impact: HIGH (avoids repeated file/network I/O per request)**\n\nWhen loading static assets (fonts, logos, images, config files) in route handlers or server functions, hoist the I/O operation to module level. Module-level code runs once when the module is first imported, not on every request. This eliminates redundant file system reads or network fetches that would otherwise run on every invocation.\n\n**Incorrect: reads font file on every request**\n\n**Correct: loads once at module initialization**\n\n**Alternative: synchronous file reads with Node.js fs**\n\n**General Node.js example: loading config or templates**\n\n**When to use this pattern:**\n\n- Loading fonts for OG image generation\n\n- Loading static logos, icons, or watermarks\n\n- Reading configuration files that don't change at runtime\n\n- Loading email templates or other static templates\n\n- Any static asset that's the same across all requests\n\n**When NOT to use this pattern:**\n\n- Assets that vary per request or user\n\n- Files that may change during runtime (use caching with TTL instead)\n\n- Large files that would consume too much memory if kept loaded\n\n- Sensitive data that shouldn't persist in memory\n\n**With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute):** Module-level caching is especially effective because multiple concurrent requests share the same function instance. The static assets stay loaded in memory across requests without cold start penalties.\n\n**In traditional serverless:** Each cold start re-executes module-level code, but subsequent warm invocations reuse the loaded assets until the instance is recycled.\n\n### 3.5 Minimize Serialization at RSC Boundaries\n\n**Impact: HIGH (reduces data transfer size)**\n\nThe React Server/Client boundary serializes all object properties into strings and embeds them in the HTML response and subsequent RSC requests. This serialized data directly impacts page weight and load time, so **size matters a lot**. Only pass fields that the client actually uses.\n\n**Incorrect: serializes all 50 fields**\n\n```tsx\nasync function Page() {\n  const user = await fetchUser()  // 50 fields\n  return <Profile user={user} />\n}\n\n'use client'\nfunction Profile({ user }: { user: User }) {\n  return <div>{user.name}</div>  // uses 1 field\n}\n```\n\n**Correct: serializes only 1 field**\n\n```tsx\nasync function Page() {\n  const user = await fetchUser()\n  return <Profile name={user.name} />\n}\n\n'use client'\nfunction Profile({ name }: { name: string }) {\n  return <div>{name}</div>\n}\n```\n\n### 3.6 Parallel Data Fetching with Component Composition\n\n**Impact: CRITICAL (eliminates server-side waterfalls)**\n\nReact Server Components execute sequentially within a tree. Restructure with composition to parallelize data fetching.\n\n**Incorrect: Sidebar waits for Page's fetch to complete**\n\n```tsx\nexport default async function Page() {\n  const header = await fetchHeader()\n  return (\n    <div>\n      <div>{header}</div>\n      <Sidebar />\n    </div>\n  )\n}\n\nasync function Sidebar() {\n  const items = await fetchSidebarItems()\n  return <nav>{items.map(renderItem)}</nav>\n}\n```\n\n**Correct: both fetch simultaneously**\n\n```tsx\nasync function Header() {\n  const data = await fetchHeader()\n  return <div>{data}</div>\n}\n\nasync function Sidebar() {\n  const items = await fetchSidebarItems()\n  return <nav>{items.map(renderItem)}</nav>\n}\n\nexport default function Page() {\n  return (\n    <div>\n      <Header />\n      <Sidebar />\n    </div>\n  )\n}\n```\n\n**Alternative with children prop:**\n\n```tsx\nasync function Header() {\n  const data = await fetchHeader()\n  return <div>{data}</div>\n}\n\nasync function Sidebar() {\n  const items = await fetchSidebarItems()\n  return <nav>{items.map(renderItem)}</nav>\n}\n\nfunction Layout({ children }: { children: ReactNode }) {\n  return (\n    <div>\n      <Header />\n      {children}\n    </div>\n  )\n}\n\nexport default function Page() {\n  return (\n    <Layout>\n      <Sidebar />\n    </Layout>\n  )\n}\n```\n\n### 3.7 Per-Request Deduplication with React.cache()\n\n**Impact: MEDIUM (deduplicates within request)**\n\nUse `React.cache()` for server-side request deduplication. Authentication and database queries benefit most.\n\n**Usage:**\n\n```typescript\nimport { cache } from 'react'\n\nexport const getCurrentUser = cache(async () => {\n  const session = await auth()\n  if (!session?.user?.id) return null\n  return await db.user.findUnique({\n    where: { id: session.user.id }\n  })\n})\n```\n\nWithin a single request, multiple calls to `getCurrentUser()` execute the query only once.\n\n**Avoid inline objects as arguments:**\n\n`React.cache()` uses shallow equality (`Object.is`) to determine cache hits. Inline objects create new references each call, preventing cache hits.\n\n**Incorrect: always cache miss**\n\n```typescript\nconst getUser = cache(async (params: { uid: number }) => {\n  return await db.user.findUnique({ where: { id: params.uid } })\n})\n\n// Each call creates new object, never hits cache\ngetUser({ uid: 1 })\ngetUser({ uid: 1 })  // Cache miss, runs query again\n```\n\n**Correct: cache hit**\n\n```typescript\nconst params = { uid: 1 }\ngetUser(params)  // Query runs\ngetUser(params)  // Cache hit (same reference)\n```\n\nIf you must pass objects, pass the same reference:\n\n**Next.js-Specific Note:**\n\nIn Next.js, the `fetch` API is automatically extended with request memoization. Requests with the same URL and options are automatically deduplicated within a single request, so you don't need `React.cache()` for `fetch` calls. However, `React.cache()` is still essential for other async tasks:\n\n- Database queries (Prisma, Drizzle, etc.)\n\n- Heavy computations\n\n- Authentication checks\n\n- File system operations\n\n- Any non-fetch async work\n\nUse `React.cache()` to deduplicate these operations across your component tree.\n\nReference: [https://react.dev/reference/react/cache](https://react.dev/reference/react/cache)\n\n### 3.8 Use after() for Non-Blocking Operations\n\n**Impact: MEDIUM (faster response times)**\n\nUse Next.js's `after()` to schedule work that should execute after a response is sent. This prevents logging, analytics, and other side effects from blocking the response.\n\n**Incorrect: blocks response**\n\n```tsx\nimport { logUserAction } from '@/app/utils'\n\nexport async function POST(request: Request) {\n  // Perform mutation\n  await updateDatabase(request)\n  \n  // Logging blocks the response\n  const userAgent = request.headers.get('user-agent') || 'unknown'\n  await logUserAction({ userAgent })\n  \n  return new Response(JSON.stringify({ status: 'success' }), {\n    status: 200,\n    headers: { 'Content-Type': 'application/json' }\n  })\n}\n```\n\n**Correct: non-blocking**\n\n```tsx\nimport { after } from 'next/server'\nimport { headers, cookies } from 'next/headers'\nimport { logUserAction } from '@/app/utils'\n\nexport async function POST(request: Request) {\n  // Perform mutation\n  await updateDatabase(request)\n  \n  // Log after response is sent\n  after(async () => {\n    const userAgent = (await headers()).get('user-agent') || 'unknown'\n    const sessionCookie = (await cookies()).get('session-id')?.value || 'anonymous'\n    \n    logUserAction({ sessionCookie, userAgent })\n  })\n  \n  return new Response(JSON.stringify({ status: 'success' }), {\n    status: 200,\n    headers: { 'Content-Type': 'application/json' }\n  })\n}\n```\n\nThe response is sent immediately while logging happens in the background.\n\n**Common use cases:**\n\n- Analytics tracking\n\n- Audit logging\n\n- Sending notifications\n\n- Cache invalidation\n\n- Cleanup tasks\n\n**Important notes:**\n\n- `after()` runs even if the response fails or redirects\n\n- Works in Server Actions, Route Handlers, and Server Components\n\nReference: [https://nextjs.org/docs/app/api-reference/functions/after](https://nextjs.org/docs/app/api-reference/functions/after)\n\n---\n\n## 4. Client-Side Data Fetching\n\n**Impact: MEDIUM-HIGH**\n\nAutomatic deduplication and efficient data fetching patterns reduce redundant network requests.\n\n### 4.1 Deduplicate Global Event Listeners\n\n**Impact: LOW (single listener for N components)**\n\nUse `useSWRSubscription()` to share global event listeners across component instances.\n\n**Incorrect: N instances = N listeners**\n\n```tsx\nfunction useKeyboardShortcut(key: string, callback: () => void) {\n  useEffect(() => {\n    const handler = (e: KeyboardEvent) => {\n      if (e.metaKey && e.key === key) {\n        callback()\n      }\n    }\n    window.addEventListener('keydown', handler)\n    return () => window.removeEventListener('keydown', handler)\n  }, [key, callback])\n}\n```\n\nWhen using the `useKeyboardShortcut` hook multiple times, each instance will register a new listener.\n\n**Correct: N instances = 1 listener**\n\n```tsx\nimport useSWRSubscription from 'swr/subscription'\n\n// Module-level Map to track callbacks per key\nconst keyCallbacks = new Map<string, Set<() => void>>()\n\nfunction useKeyboardShortcut(key: string, callback: () => void) {\n  // Register this callback in the Map\n  useEffect(() => {\n    if (!keyCallbacks.has(key)) {\n      keyCallbacks.set(key, new Set())\n    }\n    keyCallbacks.get(key)!.add(callback)\n\n    return () => {\n      const set = keyCallbacks.get(key)\n      if (set) {\n        set.delete(callback)\n        if (set.size === 0) {\n          keyCallbacks.delete(key)\n        }\n      }\n    }\n  }, [key, callback])\n\n  useSWRSubscription('global-keydown', () => {\n    const handler = (e: KeyboardEvent) => {\n      if (e.metaKey && keyCallbacks.has(e.key)) {\n        keyCallbacks.get(e.key)!.forEach(cb => cb())\n      }\n    }\n    window.addEventListener('keydown', handler)\n    return () => window.removeEventListener('keydown', handler)\n  })\n}\n\nfunction Profile() {\n  // Multiple shortcuts will share the same listener\n  useKeyboardShortcut('p', () => { /* ... */ }) \n  useKeyboardShortcut('k', () => { /* ... */ })\n  // ...\n}\n```\n\n### 4.2 Use Passive Event Listeners for Scrolling Performance\n\n**Impact: MEDIUM (eliminates scroll delay caused by event listeners)**\n\nAdd `{ passive: true }` to touch and wheel event listeners to enable immediate scrolling. Browsers normally wait for listeners to finish to check if `preventDefault()` is called, causing scroll delay.\n\n**Incorrect:**\n\n```typescript\nuseEffect(() => {\n  const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)\n  const handleWheel = (e: WheelEvent) => console.log(e.deltaY)\n  \n  document.addEventListener('touchstart', handleTouch)\n  document.addEventListener('wheel', handleWheel)\n  \n  return () => {\n    document.removeEventListener('touchstart', handleTouch)\n    document.removeEventListener('wheel', handleWheel)\n  }\n}, [])\n```\n\n**Correct:**\n\n```typescript\nuseEffect(() => {\n  const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)\n  const handleWheel = (e: WheelEvent) => console.log(e.deltaY)\n  \n  document.addEventListener('touchstart', handleTouch, { passive: true })\n  document.addEventListener('wheel', handleWheel, { passive: true })\n  \n  return () => {\n    document.removeEventListener('touchstart', handleTouch)\n    document.removeEventListener('wheel', handleWheel)\n  }\n}, [])\n```\n\n**Use passive when:** tracking/analytics, logging, any listener that doesn't call `preventDefault()`.\n\n**Don't use passive when:** implementing custom swipe gestures, custom zoom controls, or any listener that needs `preventDefault()`.\n\n### 4.3 Use SWR for Automatic Deduplication\n\n**Impact: MEDIUM-HIGH (automatic deduplication)**\n\nSWR enables request deduplication, caching, and revalidation across component instances.\n\n**Incorrect: no deduplication, each instance fetches**\n\n```tsx\nfunction UserList() {\n  const [users, setUsers] = useState([])\n  useEffect(() => {\n    fetch('/api/users')\n      .then(r => r.json())\n      .then(setUsers)\n  }, [])\n}\n```\n\n**Correct: multiple instances share one request**\n\n```tsx\nimport useSWR from 'swr'\n\nfunction UserList() {\n  const { data: users } = useSWR('/api/users', fetcher)\n}\n```\n\n**For immutable data:**\n\n```tsx\nimport { useImmutableSWR } from '@/lib/swr'\n\nfunction StaticContent() {\n  const { data } = useImmutableSWR('/api/config', fetcher)\n}\n```\n\n**For mutations:**\n\n```tsx\nimport { useSWRMutation } from 'swr/mutation'\n\nfunction UpdateButton() {\n  const { trigger } = useSWRMutation('/api/user', updateUser)\n  return <button onClick={() => trigger()}>Update</button>\n}\n```\n\nReference: [https://swr.vercel.app](https://swr.vercel.app)\n\n### 4.4 Version and Minimize localStorage Data\n\n**Impact: MEDIUM (prevents schema conflicts, reduces storage size)**\n\nAdd version prefix to keys and store only needed fields. Prevents schema conflicts and accidental storage of sensitive data.\n\n**Incorrect:**\n\n```typescript\n// No version, stores everything, no error handling\nlocalStorage.setItem('userConfig', JSON.stringify(fullUserObject))\nconst data = localStorage.getItem('userConfig')\n```\n\n**Correct:**\n\n```typescript\nconst VERSION = 'v2'\n\nfunction saveConfig(config: { theme: string; language: string }) {\n  try {\n    localStorage.setItem(`userConfig:${VERSION}`, JSON.stringify(config))\n  } catch {\n    // Throws in incognito/private browsing, quota exceeded, or disabled\n  }\n}\n\nfunction loadConfig() {\n  try {\n    const data = localStorage.getItem(`userConfig:${VERSION}`)\n    return data ? JSON.parse(data) : null\n  } catch {\n    return null\n  }\n}\n\n// Migration from v1 to v2\nfunction migrate() {\n  try {\n    const v1 = localStorage.getItem('userConfig:v1')\n    if (v1) {\n      const old = JSON.parse(v1)\n      saveConfig({ theme: old.darkMode ? 'dark' : 'light', language: old.lang })\n      localStorage.removeItem('userConfig:v1')\n    }\n  } catch {}\n}\n```\n\n**Store minimal fields from server responses:**\n\n```typescript\n// User object has 20+ fields, only store what UI needs\nfunction cachePrefs(user: FullUser) {\n  try {\n    localStorage.setItem('prefs:v1', JSON.stringify({\n      theme: user.preferences.theme,\n      notifications: user.preferences.notifications\n    }))\n  } catch {}\n}\n```\n\n**Always wrap in try-catch:** `getItem()` and `setItem()` throw in incognito/private browsing (Safari, Firefox), when quota exceeded, or when disabled.\n\n**Benefits:** Schema evolution via versioning, reduced storage size, prevents storing tokens/PII/internal flags.\n\n---\n\n## 5. Re-render Optimization\n\n**Impact: MEDIUM**\n\nReducing unnecessary re-renders minimizes wasted computation and improves UI responsiveness.\n\n### 5.1 Calculate Derived State During Rendering\n\n**Impact: MEDIUM (avoids redundant renders and state drift)**\n\nIf a value can be computed from current props/state, do not store it in state or update it in an effect. Derive it during render to avoid extra renders and state drift. Do not set state in effects solely in response to prop changes; prefer derived values or keyed resets instead.\n\n**Incorrect: redundant state and effect**\n\n```tsx\nfunction Form() {\n  const [firstName, setFirstName] = useState('First')\n  const [lastName, setLastName] = useState('Last')\n  const [fullName, setFullName] = useState('')\n\n  useEffect(() => {\n    setFullName(firstName + ' ' + lastName)\n  }, [firstName, lastName])\n\n  return <p>{fullName}</p>\n}\n```\n\n**Correct: derive during render**\n\n```tsx\nfunction Form() {\n  const [firstName, setFirstName] = useState('First')\n  const [lastName, setLastName] = useState('Last')\n  const fullName = firstName + ' ' + lastName\n\n  return <p>{fullName}</p>\n}\n```\n\nReference: [https://react.dev/learn/you-might-not-need-an-effect](https://react.dev/learn/you-might-not-need-an-effect)\n\n### 5.2 Defer State Reads to Usage Point\n\n**Impact: MEDIUM (avoids unnecessary subscriptions)**\n\nDon't subscribe to dynamic state (searchParams, localStorage) if you only read it inside callbacks.\n\n**Incorrect: subscribes to all searchParams changes**\n\n```tsx\nfunction ShareButton({ chatId }: { chatId: string }) {\n  const searchParams = useSearchParams()\n\n  const handleShare = () => {\n    const ref = searchParams.get('ref')\n    shareChat(chatId, { ref })\n  }\n\n  return <button onClick={handleShare}>Share</button>\n}\n```\n\n**Correct: reads on demand, no subscription**\n\n```tsx\nfunction ShareButton({ chatId }: { chatId: string }) {\n  const handleShare = () => {\n    const params = new URLSearchParams(window.location.search)\n    const ref = params.get('ref')\n    shareChat(chatId, { ref })\n  }\n\n  return <button onClick={handleShare}>Share</button>\n}\n```\n\n### 5.3 Do not wrap a simple expression with a primitive result type in useMemo\n\n**Impact: LOW-MEDIUM (wasted computation on every render)**\n\nWhen an expression is simple (few logical or arithmetical operators) and has a primitive result type (boolean, number, string), do not wrap it in `useMemo`.\n\nCalling `useMemo` and comparing hook dependencies may consume more resources than the expression itself.\n\n**Incorrect:**\n\n```tsx\nfunction Header({ user, notifications }: Props) {\n  const isLoading = useMemo(() => {\n    return user.isLoading || notifications.isLoading\n  }, [user.isLoading, notifications.isLoading])\n\n  if (isLoading) return <Skeleton />\n  // return some markup\n}\n```\n\n**Correct:**\n\n```tsx\nfunction Header({ user, notifications }: Props) {\n  const isLoading = user.isLoading || notifications.isLoading\n\n  if (isLoading) return <Skeleton />\n  // return some markup\n}\n```\n\n### 5.4 Extract Default Non-primitive Parameter Value from Memoized Component to Constant\n\n**Impact: MEDIUM (restores memoization by using a constant for default value)**\n\nWhen memoized component has a default value for some non-primitive optional parameter, such as an array, function, or object, calling the component without that parameter results in broken memoization. This is because new value instances are created on every rerender, and they do not pass strict equality comparison in `memo()`.\n\nTo address this issue, extract the default value into a constant.\n\n**Incorrect: `onClick` has different values on every rerender**\n\n```tsx\nconst UserAvatar = memo(function UserAvatar({ onClick = () => {} }: { onClick?: () => void }) {\n  // ...\n})\n\n// Used without optional onClick\n<UserAvatar />\n```\n\n**Correct: stable default value**\n\n```tsx\nconst NOOP = () => {};\n\nconst UserAvatar = memo(function UserAvatar({ onClick = NOOP }: { onClick?: () => void }) {\n  // ...\n})\n\n// Used without optional onClick\n<UserAvatar />\n```\n\n### 5.5 Extract to Memoized Components\n\n**Impact: MEDIUM (enables early returns)**\n\nExtract expensive work into memoized components to enable early returns before computation.\n\n**Incorrect: computes avatar even when loading**\n\n```tsx\nfunction Profile({ user, loading }: Props) {\n  const avatar = useMemo(() => {\n    const id = computeAvatarId(user)\n    return <Avatar id={id} />\n  }, [user])\n\n  if (loading) return <Skeleton />\n  return <div>{avatar}</div>\n}\n```\n\n**Correct: skips computation when loading**\n\n```tsx\nconst UserAvatar = memo(function UserAvatar({ user }: { user: User }) {\n  const id = useMemo(() => computeAvatarId(user), [user])\n  return <Avatar id={id} />\n})\n\nfunction Profile({ user, loading }: Props) {\n  if (loading) return <Skeleton />\n  return (\n    <div>\n      <UserAvatar user={user} />\n    </div>\n  )\n}\n```\n\n**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, manual memoization with `memo()` and `useMemo()` is not necessary. The compiler automatically optimizes re-renders.\n\n### 5.6 Narrow Effect Dependencies\n\n**Impact: LOW (minimizes effect re-runs)**\n\nSpecify primitive dependencies instead of objects to minimize effect re-runs.\n\n**Incorrect: re-runs on any user field change**\n\n```tsx\nuseEffect(() => {\n  console.log(user.id)\n}, [user])\n```\n\n**Correct: re-runs only when id changes**\n\n```tsx\nuseEffect(() => {\n  console.log(user.id)\n}, [user.id])\n```\n\n**For derived state, compute outside effect:**\n\n```tsx\n// Incorrect: runs on width=767, 766, 765...\nuseEffect(() => {\n  if (width < 768) {\n    enableMobileMode()\n  }\n}, [width])\n\n// Correct: runs only on boolean transition\nconst isMobile = width < 768\nuseEffect(() => {\n  if (isMobile) {\n    enableMobileMode()\n  }\n}, [isMobile])\n```\n\n### 5.7 Put Interaction Logic in Event Handlers\n\n**Impact: MEDIUM (avoids effect re-runs and duplicate side effects)**\n\nIf a side effect is triggered by a specific user action (submit, click, drag), run it in that event handler. Do not model the action as state + effect; it makes effects re-run on unrelated changes and can duplicate the action.\n\n**Incorrect: event modeled as state + effect**\n\n```tsx\nfunction Form() {\n  const [submitted, setSubmitted] = useState(false)\n  const theme = useContext(ThemeContext)\n\n  useEffect(() => {\n    if (submitted) {\n      post('/api/register')\n      showToast('Registered', theme)\n    }\n  }, [submitted, theme])\n\n  return <button onClick={() => setSubmitted(true)}>Submit</button>\n}\n```\n\n**Correct: do it in the handler**\n\n```tsx\nfunction Form() {\n  const theme = useContext(ThemeContext)\n\n  function handleSubmit() {\n    post('/api/register')\n    showToast('Registered', theme)\n  }\n\n  return <button onClick={handleSubmit}>Submit</button>\n}\n```\n\nReference: [https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler](https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler)\n\n### 5.8 Subscribe to Derived State\n\n**Impact: MEDIUM (reduces re-render frequency)**\n\nSubscribe to derived boolean state instead of continuous values to reduce re-render frequency.\n\n**Incorrect: re-renders on every pixel change**\n\n```tsx\nfunction Sidebar() {\n  const width = useWindowWidth()  // updates continuously\n  const isMobile = width < 768\n  return <nav className={isMobile ? 'mobile' : 'desktop'} />\n}\n```\n\n**Correct: re-renders only when boolean changes**\n\n```tsx\nfunction Sidebar() {\n  const isMobile = useMediaQuery('(max-width: 767px)')\n  return <nav className={isMobile ? 'mobile' : 'desktop'} />\n}\n```\n\n### 5.9 Use Functional setState Updates\n\n**Impact: MEDIUM (prevents stale closures and unnecessary callback recreations)**\n\nWhen updating state based on the current state value, use the functional update form of setState instead of directly referencing the state variable. This prevents stale closures, eliminates unnecessary dependencies, and creates stable callback references.\n\n**Incorrect: requires state as dependency**\n\n```tsx\nfunction TodoList() {\n  const [items, setItems] = useState(initialItems)\n  \n  // Callback must depend on items, recreated on every items change\n  const addItems = useCallback((newItems: Item[]) => {\n    setItems([...items, ...newItems])\n  }, [items])  // ❌ items dependency causes recreations\n  \n  // Risk of stale closure if dependency is forgotten\n  const removeItem = useCallback((id: string) => {\n    setItems(items.filter(item => item.id !== id))\n  }, [])  // ❌ Missing items dependency - will use stale items!\n  \n  return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />\n}\n```\n\nThe first callback is recreated every time `items` changes, which can cause child components to re-render unnecessarily. The second callback has a stale closure bug—it will always reference the initial `items` value.\n\n**Correct: stable callbacks, no stale closures**\n\n```tsx\nfunction TodoList() {\n  const [items, setItems] = useState(initialItems)\n  \n  // Stable callback, never recreated\n  const addItems = useCallback((newItems: Item[]) => {\n    setItems(curr => [...curr, ...newItems])\n  }, [])  // ✅ No dependencies needed\n  \n  // Always uses latest state, no stale closure risk\n  const removeItem = useCallback((id: string) => {\n    setItems(curr => curr.filter(item => item.id !== id))\n  }, [])  // ✅ Safe and stable\n  \n  return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />\n}\n```\n\n**Benefits:**\n\n1. **Stable callback references** - Callbacks don't need to be recreated when state changes\n\n2. **No stale closures** - Always operates on the latest state value\n\n3. **Fewer dependencies** - Simplifies dependency arrays and reduces memory leaks\n\n4. **Prevents bugs** - Eliminates the most common source of React closure bugs\n\n**When to use functional updates:**\n\n- Any setState that depends on the current state value\n\n- Inside useCallback/useMemo when state is needed\n\n- Event handlers that reference state\n\n- Async operations that update state\n\n**When direct updates are fine:**\n\n- Setting state to a static value: `setCount(0)`\n\n- Setting state from props/arguments only: `setName(newName)`\n\n- State doesn't depend on previous value\n\n**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler can automatically optimize some cases, but functional updates are still recommended for correctness and to prevent stale closure bugs.\n\n### 5.10 Use Lazy State Initialization\n\n**Impact: \n\n<!-- [TRUNCATED AT 50KB] -->",".agents/skills/vercel-react-best-practices/README.md":"# React Best Practices\n\nA structured repository for creating and maintaining React Best Practices optimized for agents and LLMs.\n\n## Structure\n\n- `rules/` - Individual rule files (one per rule)\n  - `_sections.md` - Section metadata (titles, impacts, descriptions)\n  - `_template.md` - Template for creating new rules\n  - `area-description.md` - Individual rule files\n- `src/` - Build scripts and utilities\n- `metadata.json` - Document metadata (version, organization, abstract)\n- __`AGENTS.md`__ - Compiled output (generated)\n- __`test-cases.json`__ - Test cases for LLM evaluation (generated)\n\n## Getting Started\n\n1. Install dependencies:\n   ```bash\n   pnpm install\n   ```\n\n2. Build AGENTS.md from rules:\n   ```bash\n   pnpm build\n   ```\n\n3. Validate rule files:\n   ```bash\n   pnpm validate\n   ```\n\n4. Extract test cases:\n   ```bash\n   pnpm extract-tests\n   ```\n\n## Creating a New Rule\n\n1. Copy `rules/_template.md` to `rules/area-description.md`\n2. Choose the appropriate area prefix:\n   - `async-` for Eliminating Waterfalls (Section 1)\n   - `bundle-` for Bundle Size Optimization (Section 2)\n   - `server-` for Server-Side Performance (Section 3)\n   - `client-` for Client-Side Data Fetching (Section 4)\n   - `rerender-` for Re-render Optimization (Section 5)\n   - `rendering-` for Rendering Performance (Section 6)\n   - `js-` for JavaScript Performance (Section 7)\n   - `advanced-` for Advanced Patterns (Section 8)\n3. Fill in the frontmatter and content\n4. Ensure you have clear examples with explanations\n5. Run `pnpm build` to regenerate AGENTS.md and test-cases.json\n\n## Rule File Structure\n\nEach rule file should follow this structure:\n\n```markdown\n---\ntitle: Rule Title Here\nimpact: MEDIUM\nimpactDescription: Optional description\ntags: tag1, tag2, tag3\n---\n\n## Rule Title Here\n\nBrief explanation of the rule and why it matters.\n\n**Incorrect (description of what's wrong):**\n\n```typescript\n// Bad code example\n```\n\n**Correct (description of what's right):**\n\n```typescript\n// Good code example\n```\n\nOptional explanatory text after examples.\n\nReference: [Link](https://example.com)\n\n## File Naming Convention\n\n- Files starting with `_` are special (excluded from build)\n- Rule files: `area-description.md` (e.g., `async-parallel.md`)\n- Section is automatically inferred from filename prefix\n- Rules are sorted alphabetically by title within each section\n- IDs (e.g., 1.1, 1.2) are auto-generated during build\n\n## Impact Levels\n\n- `CRITICAL` - Highest priority, major performance gains\n- `HIGH` - Significant performance improvements\n- `MEDIUM-HIGH` - Moderate-high gains\n- `MEDIUM` - Moderate performance improvements\n- `LOW-MEDIUM` - Low-medium gains\n- `LOW` - Incremental improvements\n\n## Scripts\n\n- `pnpm build` - Compile rules into AGENTS.md\n- `pnpm validate` - Validate all rule files\n- `pnpm extract-tests` - Extract test cases for LLM evaluation\n- `pnpm dev` - Build and validate\n\n## Contributing\n\nWhen adding or modifying rules:\n\n1. Use the correct filename prefix for your section\n2. Follow the `_template.md` structure\n3. Include clear bad/good examples with explanations\n4. Add appropriate tags\n5. Run `pnpm build` to regenerate AGENTS.md and test-cases.json\n6. Rules are automatically sorted by title - no need to manage numbers!\n\n## Acknowledgments\n\nOriginally created by [@shuding](https://x.com/shuding) at [Vercel](https://vercel.com).\n",".agents/skills/vercel-react-best-practices/SKILL.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2FSKILL.md -->",".agents/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md":"---\ntitle: Store Event Handlers in Refs\nimpact: LOW\nimpactDescription: stable subscriptions\ntags: advanced, hooks, refs, event-handlers, optimization\n---\n\n## Store Event Handlers in Refs\n\nStore callbacks in refs when used in effects that shouldn't re-subscribe on callback changes.\n\n**Incorrect (re-subscribes on every render):**\n\n```tsx\nfunction useWindowEvent(event: string, handler: (e) => void) {\n  useEffect(() => {\n    window.addEventListener(event, handler)\n    return () => window.removeEventListener(event, handler)\n  }, [event, handler])\n}\n```\n\n**Correct (stable subscription):**\n\n```tsx\nfunction useWindowEvent(event: string, handler: (e) => void) {\n  const handlerRef = useRef(handler)\n  useEffect(() => {\n    handlerRef.current = handler\n  }, [handler])\n\n  useEffect(() => {\n    const listener = (e) => handlerRef.current(e)\n    window.addEventListener(event, listener)\n    return () => window.removeEventListener(event, listener)\n  }, [event])\n}\n```\n\n**Alternative: use `useEffectEvent` if you're on latest React:**\n\n```tsx\nimport { useEffectEvent } from 'react'\n\nfunction useWindowEvent(event: string, handler: (e) => void) {\n  const onEvent = useEffectEvent(handler)\n\n  useEffect(() => {\n    window.addEventListener(event, onEvent)\n    return () => window.removeEventListener(event, onEvent)\n  }, [event])\n}\n```\n\n`useEffectEvent` provides a cleaner API for the same pattern: it creates a stable function reference that always calls the latest version of the handler.\n",".agents/skills/vercel-react-best-practices/rules/advanced-init-once.md":"---\ntitle: Initialize App Once, Not Per Mount\nimpact: LOW-MEDIUM\nimpactDescription: avoids duplicate init in development\ntags: initialization, useEffect, app-startup, side-effects\n---\n\n## Initialize App Once, Not Per Mount\n\nDo not put app-wide initialization that must run once per app load inside `useEffect([])` of a component. Components can remount and effects will re-run. Use a module-level guard or top-level init in the entry module instead.\n\n**Incorrect (runs twice in dev, re-runs on remount):**\n\n```tsx\nfunction Comp() {\n  useEffect(() => {\n    loadFromStorage()\n    checkAuthToken()\n  }, [])\n\n  // ...\n}\n```\n\n**Correct (once per app load):**\n\n```tsx\nlet didInit = false\n\nfunction Comp() {\n  useEffect(() => {\n    if (didInit) return\n    didInit = true\n    loadFromStorage()\n    checkAuthToken()\n  }, [])\n\n  // ...\n}\n```\n\nReference: [Initializing the application](https://react.dev/learn/you-might-not-need-an-effect#initializing-the-application)\n",".agents/skills/vercel-react-best-practices/rules/advanced-use-latest.md":"---\ntitle: useEffectEvent for Stable Callback Refs\nimpact: LOW\nimpactDescription: prevents effect re-runs\ntags: advanced, hooks, useEffectEvent, refs, optimization\n---\n\n## useEffectEvent for Stable Callback Refs\n\nAccess latest values in callbacks without adding them to dependency arrays. Prevents effect re-runs while avoiding stale closures.\n\n**Incorrect (effect re-runs on every callback change):**\n\n```tsx\nfunction SearchInput({ onSearch }: { onSearch: (q: string) => void }) {\n  const [query, setQuery] = useState('')\n\n  useEffect(() => {\n    const timeout = setTimeout(() => onSearch(query), 300)\n    return () => clearTimeout(timeout)\n  }, [query, onSearch])\n}\n```\n\n**Correct (using React's useEffectEvent):**\n\n```tsx\nimport { useEffectEvent } from 'react';\n\nfunction SearchInput({ onSearch }: { onSearch: (q: string) => void }) {\n  const [query, setQuery] = useState('')\n  const onSearchEvent = useEffectEvent(onSearch)\n\n  useEffect(() => {\n    const timeout = setTimeout(() => onSearchEvent(query), 300)\n    return () => clearTimeout(timeout)\n  }, [query])\n}\n```\n",".agents/skills/vercel-react-best-practices/rules/async-api-routes.md":"---\ntitle: Prevent Waterfall Chains in API Routes\nimpact: CRITICAL\nimpactDescription: 2-10× improvement\ntags: api-routes, server-actions, waterfalls, parallelization\n---\n\n## Prevent Waterfall Chains in API Routes\n\nIn API routes and Server Actions, start independent operations immediately, even if you don't await them yet.\n\n**Incorrect (config waits for auth, data waits for both):**\n\n```typescript\nexport async function GET(request: Request) {\n  const session = await auth()\n  const config = await fetchConfig()\n  const data = await fetchData(session.user.id)\n  return Response.json({ data, config })\n}\n```\n\n**Correct (auth and config start immediately):**\n\n```typescript\nexport async function GET(request: Request) {\n  const sessionPromise = auth()\n  const configPromise = fetchConfig()\n  const session = await sessionPromise\n  const [config, data] = await Promise.all([\n    configPromise,\n    fetchData(session.user.id)\n  ])\n  return Response.json({ data, config })\n}\n```\n\nFor operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization).\n",".agents/skills/vercel-react-best-practices/rules/async-defer-await.md":"---\ntitle: Defer Await Until Needed\nimpact: HIGH\nimpactDescription: avoids blocking unused code paths\ntags: async, await, conditional, optimization\n---\n\n## Defer Await Until Needed\n\nMove `await` operations into the branches where they're actually used to avoid blocking code paths that don't need them.\n\n**Incorrect (blocks both branches):**\n\n```typescript\nasync function handleRequest(userId: string, skipProcessing: boolean) {\n  const userData = await fetchUserData(userId)\n  \n  if (skipProcessing) {\n    // Returns immediately but still waited for userData\n    return { skipped: true }\n  }\n  \n  // Only this branch uses userData\n  return processUserData(userData)\n}\n```\n\n**Correct (only blocks when needed):**\n\n```typescript\nasync function handleRequest(userId: string, skipProcessing: boolean) {\n  if (skipProcessing) {\n    // Returns immediately without waiting\n    return { skipped: true }\n  }\n  \n  // Fetch only when needed\n  const userData = await fetchUserData(userId)\n  return processUserData(userData)\n}\n```\n\n**Another example (early return optimization):**\n\n```typescript\n// Incorrect: always fetches permissions\nasync function updateResource(resourceId: string, userId: string) {\n  const permissions = await fetchPermissions(userId)\n  const resource = await getResource(resourceId)\n  \n  if (!resource) {\n    return { error: 'Not found' }\n  }\n  \n  if (!permissions.canEdit) {\n    return { error: 'Forbidden' }\n  }\n  \n  return await updateResourceData(resource, permissions)\n}\n\n// Correct: fetches only when needed\nasync function updateResource(resourceId: string, userId: string) {\n  const resource = await getResource(resourceId)\n  \n  if (!resource) {\n    return { error: 'Not found' }\n  }\n  \n  const permissions = await fetchPermissions(userId)\n  \n  if (!permissions.canEdit) {\n    return { error: 'Forbidden' }\n  }\n  \n  return await updateResourceData(resource, permissions)\n}\n```\n\nThis optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive.\n",".agents/skills/vercel-react-best-practices/rules/async-dependencies.md":"---\ntitle: Dependency-Based Parallelization\nimpact: CRITICAL\nimpactDescription: 2-10× improvement\ntags: async, parallelization, dependencies, better-all\n---\n\n## Dependency-Based Parallelization\n\nFor operations with partial dependencies, use `better-all` to maximize parallelism. It automatically starts each task at the earliest possible moment.\n\n**Incorrect (profile waits for config unnecessarily):**\n\n```typescript\nconst [user, config] = await Promise.all([\n  fetchUser(),\n  fetchConfig()\n])\nconst profile = await fetchProfile(user.id)\n```\n\n**Correct (config and profile run in parallel):**\n\n```typescript\nimport { all } from 'better-all'\n\nconst { user, config, profile } = await all({\n  async user() { return fetchUser() },\n  async config() { return fetchConfig() },\n  async profile() {\n    return fetchProfile((await this.$.user).id)\n  }\n})\n```\n\n**Alternative without extra dependencies:**\n\nWe can also create all the promises first, and do `Promise.all()` at the end.\n\n```typescript\nconst userPromise = fetchUser()\nconst profilePromise = userPromise.then(user => fetchProfile(user.id))\n\nconst [user, config, profile] = await Promise.all([\n  userPromise,\n  fetchConfig(),\n  profilePromise\n])\n```\n\nReference: [https://github.com/shuding/better-all](https://github.com/shuding/better-all)\n",".agents/skills/vercel-react-best-practices/rules/async-parallel.md":"---\ntitle: Promise.all() for Independent Operations\nimpact: CRITICAL\nimpactDescription: 2-10× improvement\ntags: async, parallelization, promises, waterfalls\n---\n\n## Promise.all() for Independent Operations\n\nWhen async operations have no interdependencies, execute them concurrently using `Promise.all()`.\n\n**Incorrect (sequential execution, 3 round trips):**\n\n```typescript\nconst user = await fetchUser()\nconst posts = await fetchPosts()\nconst comments = await fetchComments()\n```\n\n**Correct (parallel execution, 1 round trip):**\n\n```typescript\nconst [user, posts, comments] = await Promise.all([\n  fetchUser(),\n  fetchPosts(),\n  fetchComments()\n])\n```\n",".agents/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md":"---\ntitle: Strategic Suspense Boundaries\nimpact: HIGH\nimpactDescription: faster initial paint\ntags: async, suspense, streaming, layout-shift\n---\n\n## Strategic Suspense Boundaries\n\nInstead of awaiting data in async components before returning JSX, use Suspense boundaries to show the wrapper UI faster while data loads.\n\n**Incorrect (wrapper blocked by data fetching):**\n\n```tsx\nasync function Page() {\n  const data = await fetchData() // Blocks entire page\n  \n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <div>\n        <DataDisplay data={data} />\n      </div>\n      <div>Footer</div>\n    </div>\n  )\n}\n```\n\nThe entire layout waits for data even though only the middle section needs it.\n\n**Correct (wrapper shows immediately, data streams in):**\n\n```tsx\nfunction Page() {\n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <div>\n        <Suspense fallback={<Skeleton />}>\n          <DataDisplay />\n        </Suspense>\n      </div>\n      <div>Footer</div>\n    </div>\n  )\n}\n\nasync function DataDisplay() {\n  const data = await fetchData() // Only blocks this component\n  return <div>{data.content}</div>\n}\n```\n\nSidebar, Header, and Footer render immediately. Only DataDisplay waits for data.\n\n**Alternative (share promise across components):**\n\n```tsx\nfunction Page() {\n  // Start fetch immediately, but don't await\n  const dataPromise = fetchData()\n  \n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <Suspense fallback={<Skeleton />}>\n        <DataDisplay dataPromise={dataPromise} />\n        <DataSummary dataPromise={dataPromise} />\n      </Suspense>\n      <div>Footer</div>\n    </div>\n  )\n}\n\nfunction DataDisplay({ dataPromise }: { dataPromise: Promise<Data> }) {\n  const data = use(dataPromise) // Unwraps the promise\n  return <div>{data.content}</div>\n}\n\nfunction DataSummary({ dataPromise }: { dataPromise: Promise<Data> }) {\n  const data = use(dataPromise) // Reuses the same promise\n  return <div>{data.summary}</div>\n}\n```\n\nBoth components share the same promise, so only one fetch occurs. Layout renders immediately while both components wait together.\n\n**When NOT to use this pattern:**\n\n- Critical data needed for layout decisions (affects positioning)\n- SEO-critical content above the fold\n- Small, fast queries where suspense overhead isn't worth it\n- When you want to avoid layout shift (loading → content jump)\n\n**Trade-off:** Faster initial paint vs potential layout shift. Choose based on your UX priorities.\n",".agents/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md":"---\ntitle: Avoid Barrel File Imports\nimpact: CRITICAL\nimpactDescription: 200-800ms import cost, slow builds\ntags: bundle, imports, tree-shaking, barrel-files, performance\n---\n\n## Avoid Barrel File Imports\n\nImport directly from source files instead of barrel files to avoid loading thousands of unused modules. **Barrel files** are entry points that re-export multiple modules (e.g., `index.js` that does `export * from './module'`).\n\nPopular icon and component libraries can have **up to 10,000 re-exports** in their entry file. For many React packages, **it takes 200-800ms just to import them**, affecting both development speed and production cold starts.\n\n**Why tree-shaking doesn't help:** When a library is marked as external (not bundled), the bundler can't optimize it. If you bundle it to enable tree-shaking, builds become substantially slower analyzing the entire module graph.\n\n**Incorrect (imports entire library):**\n\n```tsx\nimport { Check, X, Menu } from 'lucide-react'\n// Loads 1,583 modules, takes ~2.8s extra in dev\n// Runtime cost: 200-800ms on every cold start\n\nimport { Button, TextField } from '@mui/material'\n// Loads 2,225 modules, takes ~4.2s extra in dev\n```\n\n**Correct (imports only what you need):**\n\n```tsx\nimport Check from 'lucide-react/dist/esm/icons/check'\nimport X from 'lucide-react/dist/esm/icons/x'\nimport Menu from 'lucide-react/dist/esm/icons/menu'\n// Loads only 3 modules (~2KB vs ~1MB)\n\nimport Button from '@mui/material/Button'\nimport TextField from '@mui/material/TextField'\n// Loads only what you use\n```\n\n**Alternative (Next.js 13.5+):**\n\n```js\n// next.config.js - use optimizePackageImports\nmodule.exports = {\n  experimental: {\n    optimizePackageImports: ['lucide-react', '@mui/material']\n  }\n}\n\n// Then you can keep the ergonomic barrel imports:\nimport { Check, X, Menu } from 'lucide-react'\n// Automatically transformed to direct imports at build time\n```\n\nDirect imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR.\n\nLibraries commonly affected: `lucide-react`, `@mui/material`, `@mui/icons-material`, `@tabler/icons-react`, `react-icons`, `@headlessui/react`, `@radix-ui/react-*`, `lodash`, `ramda`, `date-fns`, `rxjs`, `react-use`.\n\nReference: [How we optimized package imports in Next.js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)\n",".agents/skills/vercel-react-best-practices/rules/bundle-conditional.md":"---\ntitle: Conditional Module Loading\nimpact: HIGH\nimpactDescription: loads large data only when needed\ntags: bundle, conditional-loading, lazy-loading\n---\n\n## Conditional Module Loading\n\nLoad large data or modules only when a feature is activated.\n\n**Example (lazy-load animation frames):**\n\n```tsx\nfunction AnimationPlayer({ enabled, setEnabled }: { enabled: boolean; setEnabled: React.Dispatch<React.SetStateAction<boolean>> }) {\n  const [frames, setFrames] = useState<Frame[] | null>(null)\n\n  useEffect(() => {\n    if (enabled && !frames && typeof window !== 'undefined') {\n      import('./animation-frames.js')\n        .then(mod => setFrames(mod.frames))\n        .catch(() => setEnabled(false))\n    }\n  }, [enabled, frames, setEnabled])\n\n  if (!frames) return <Skeleton />\n  return <Canvas frames={frames} />\n}\n```\n\nThe `typeof window !== 'undefined'` check prevents bundling this module for SSR, optimizing server bundle size and build speed.\n",".agents/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md":"---\ntitle: Defer Non-Critical Third-Party Libraries\nimpact: MEDIUM\nimpactDescription: loads after hydration\ntags: bundle, third-party, analytics, defer\n---\n\n## Defer Non-Critical Third-Party Libraries\n\nAnalytics, logging, and error tracking don't block user interaction. Load them after hydration.\n\n**Incorrect (blocks initial bundle):**\n\n```tsx\nimport { Analytics } from '@vercel/analytics/react'\n\nexport default function RootLayout({ children }) {\n  return (\n    <html>\n      <body>\n        {children}\n        <Analytics />\n      </body>\n    </html>\n  )\n}\n```\n\n**Correct (loads after hydration):**\n\n```tsx\nimport dynamic from 'next/dynamic'\n\nconst Analytics = dynamic(\n  () => import('@vercel/analytics/react').then(m => m.Analytics),\n  { ssr: false }\n)\n\nexport default function RootLayout({ children }) {\n  return (\n    <html>\n      <body>\n        {children}\n        <Analytics />\n      </body>\n    </html>\n  )\n}\n```\n",".agents/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md":"---\ntitle: Dynamic Imports for Heavy Components\nimpact: CRITICAL\nimpactDescription: directly affects TTI and LCP\ntags: bundle, dynamic-import, code-splitting, next-dynamic\n---\n\n## Dynamic Imports for Heavy Components\n\nUse `next/dynamic` to lazy-load large components not needed on initial render.\n\n**Incorrect (Monaco bundles with main chunk ~300KB):**\n\n```tsx\nimport { MonacoEditor } from './monaco-editor'\n\nfunction CodePanel({ code }: { code: string }) {\n  return <MonacoEditor value={code} />\n}\n```\n\n**Correct (Monaco loads on demand):**\n\n```tsx\nimport dynamic from 'next/dynamic'\n\nconst MonacoEditor = dynamic(\n  () => import('./monaco-editor').then(m => m.MonacoEditor),\n  { ssr: false }\n)\n\nfunction CodePanel({ code }: { code: string }) {\n  return <MonacoEditor value={code} />\n}\n```\n",".agents/skills/vercel-react-best-practices/rules/bundle-preload.md":"---\ntitle: Preload Based on User Intent\nimpact: MEDIUM\nimpactDescription: reduces perceived latency\ntags: bundle, preload, user-intent, hover\n---\n\n## Preload Based on User Intent\n\nPreload heavy bundles before they're needed to reduce perceived latency.\n\n**Example (preload on hover/focus):**\n\n```tsx\nfunction EditorButton({ onClick }: { onClick: () => void }) {\n  const preload = () => {\n    if (typeof window !== 'undefined') {\n      void import('./monaco-editor')\n    }\n  }\n\n  return (\n    <button\n      onMouseEnter={preload}\n      onFocus={preload}\n      onClick={onClick}\n    >\n      Open Editor\n    </button>\n  )\n}\n```\n\n**Example (preload when feature flag is enabled):**\n\n```tsx\nfunction FlagsProvider({ children, flags }: Props) {\n  useEffect(() => {\n    if (flags.editorEnabled && typeof window !== 'undefined') {\n      void import('./monaco-editor').then(mod => mod.init())\n    }\n  }, [flags.editorEnabled])\n\n  return <FlagsContext.Provider value={flags}>\n    {children}\n  </FlagsContext.Provider>\n}\n```\n\nThe `typeof window !== 'undefined'` check prevents bundling preloaded modules for SSR, optimizing server bundle size and build speed.\n",".agents/skills/vercel-react-best-practices/rules/client-event-listeners.md":"---\ntitle: Deduplicate Global Event Listeners\nimpact: LOW\nimpactDescription: single listener for N components\ntags: client, swr, event-listeners, subscription\n---\n\n## Deduplicate Global Event Listeners\n\nUse `useSWRSubscription()` to share global event listeners across component instances.\n\n**Incorrect (N instances = N listeners):**\n\n```tsx\nfunction useKeyboardShortcut(key: string, callback: () => void) {\n  useEffect(() => {\n    const handler = (e: KeyboardEvent) => {\n      if (e.metaKey && e.key === key) {\n        callback()\n      }\n    }\n    window.addEventListener('keydown', handler)\n    return () => window.removeEventListener('keydown', handler)\n  }, [key, callback])\n}\n```\n\nWhen using the `useKeyboardShortcut` hook multiple times, each instance will register a new listener.\n\n**Correct (N instances = 1 listener):**\n\n```tsx\nimport useSWRSubscription from 'swr/subscription'\n\n// Module-level Map to track callbacks per key\nconst keyCallbacks = new Map<string, Set<() => void>>()\n\nfunction useKeyboardShortcut(key: string, callback: () => void) {\n  // Register this callback in the Map\n  useEffect(() => {\n    if (!keyCallbacks.has(key)) {\n      keyCallbacks.set(key, new Set())\n    }\n    keyCallbacks.get(key)!.add(callback)\n\n    return () => {\n      const set = keyCallbacks.get(key)\n      if (set) {\n        set.delete(callback)\n        if (set.size === 0) {\n          keyCallbacks.delete(key)\n        }\n      }\n    }\n  }, [key, callback])\n\n  useSWRSubscription('global-keydown', () => {\n    const handler = (e: KeyboardEvent) => {\n      if (e.metaKey && keyCallbacks.has(e.key)) {\n        keyCallbacks.get(e.key)!.forEach(cb => cb())\n      }\n    }\n    window.addEventListener('keydown', handler)\n    return () => window.removeEventListener('keydown', handler)\n  })\n}\n\nfunction Profile() {\n  // Multiple shortcuts will share the same listener\n  useKeyboardShortcut('p', () => { /* ... */ }) \n  useKeyboardShortcut('k', () => { /* ... */ })\n  // ...\n}\n```\n",".agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md":"---\ntitle: Version and Minimize localStorage Data\nimpact: MEDIUM\nimpactDescription: prevents schema conflicts, reduces storage size\ntags: client, localStorage, storage, versioning, data-minimization\n---\n\n## Version and Minimize localStorage Data\n\nAdd version prefix to keys and store only needed fields. Prevents schema conflicts and accidental storage of sensitive data.\n\n**Incorrect:**\n\n```typescript\n// No version, stores everything, no error handling\nlocalStorage.setItem('userConfig', JSON.stringify(fullUserObject))\nconst data = localStorage.getItem('userConfig')\n```\n\n**Correct:**\n\n```typescript\nconst VERSION = 'v2'\n\nfunction saveConfig(config: { theme: string; language: string }) {\n  try {\n    localStorage.setItem(`userConfig:${VERSION}`, JSON.stringify(config))\n  } catch {\n    // Throws in incognito/private browsing, quota exceeded, or disabled\n  }\n}\n\nfunction loadConfig() {\n  try {\n    const data = localStorage.getItem(`userConfig:${VERSION}`)\n    return data ? JSON.parse(data) : null\n  } catch {\n    return null\n  }\n}\n\n// Migration from v1 to v2\nfunction migrate() {\n  try {\n    const v1 = localStorage.getItem('userConfig:v1')\n    if (v1) {\n      const old = JSON.parse(v1)\n      saveConfig({ theme: old.darkMode ? 'dark' : 'light', language: old.lang })\n      localStorage.removeItem('userConfig:v1')\n    }\n  } catch {}\n}\n```\n\n**Store minimal fields from server responses:**\n\n```typescript\n// User object has 20+ fields, only store what UI needs\nfunction cachePrefs(user: FullUser) {\n  try {\n    localStorage.setItem('prefs:v1', JSON.stringify({\n      theme: user.preferences.theme,\n      notifications: user.preferences.notifications\n    }))\n  } catch {}\n}\n```\n\n**Always wrap in try-catch:** `getItem()` and `setItem()` throw in incognito/private browsing (Safari, Firefox), when quota exceeded, or when disabled.\n\n**Benefits:** Schema evolution via versioning, reduced storage size, prevents storing tokens/PII/internal flags.\n",".agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md":"---\ntitle: Use Passive Event Listeners for Scrolling Performance\nimpact: MEDIUM\nimpactDescription: eliminates scroll delay caused by event listeners\ntags: client, event-listeners, scrolling, performance, touch, wheel\n---\n\n## Use Passive Event Listeners for Scrolling Performance\n\nAdd `{ passive: true }` to touch and wheel event listeners to enable immediate scrolling. Browsers normally wait for listeners to finish to check if `preventDefault()` is called, causing scroll delay.\n\n**Incorrect:**\n\n```typescript\nuseEffect(() => {\n  const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)\n  const handleWheel = (e: WheelEvent) => console.log(e.deltaY)\n  \n  document.addEventListener('touchstart', handleTouch)\n  document.addEventListener('wheel', handleWheel)\n  \n  return () => {\n    document.removeEventListener('touchstart', handleTouch)\n    document.removeEventListener('wheel', handleWheel)\n  }\n}, [])\n```\n\n**Correct:**\n\n```typescript\nuseEffect(() => {\n  const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)\n  const handleWheel = (e: WheelEvent) => console.log(e.deltaY)\n  \n  document.addEventListener('touchstart', handleTouch, { passive: true })\n  document.addEventListener('wheel', handleWheel, { passive: true })\n  \n  return () => {\n    document.removeEventListener('touchstart', handleTouch)\n    document.removeEventListener('wheel', handleWheel)\n  }\n}, [])\n```\n\n**Use passive when:** tracking/analytics, logging, any listener that doesn't call `preventDefault()`.\n\n**Don't use passive when:** implementing custom swipe gestures, custom zoom controls, or any listener that needs `preventDefault()`.\n",".agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md":"---\ntitle: Use SWR for Automatic Deduplication\nimpact: MEDIUM-HIGH\nimpactDescription: automatic deduplication\ntags: client, swr, deduplication, data-fetching\n---\n\n## Use SWR for Automatic Deduplication\n\nSWR enables request deduplication, caching, and revalidation across component instances.\n\n**Incorrect (no deduplication, each instance fetches):**\n\n```tsx\nfunction UserList() {\n  const [users, setUsers] = useState([])\n  useEffect(() => {\n    fetch('/api/users')\n      .then(r => r.json())\n      .then(setUsers)\n  }, [])\n}\n```\n\n**Correct (multiple instances share one request):**\n\n```tsx\nimport useSWR from 'swr'\n\nfunction UserList() {\n  const { data: users } = useSWR('/api/users', fetcher)\n}\n```\n\n**For immutable data:**\n\n```tsx\nimport { useImmutableSWR } from '@/lib/swr'\n\nfunction StaticContent() {\n  const { data } = useImmutableSWR('/api/config', fetcher)\n}\n```\n\n**For mutations:**\n\n```tsx\nimport { useSWRMutation } from 'swr/mutation'\n\nfunction UpdateButton() {\n  const { trigger } = useSWRMutation('/api/user', updateUser)\n  return <button onClick={() => trigger()}>Update</button>\n}\n```\n\nReference: [https://swr.vercel.app](https://swr.vercel.app)\n",".agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md":"---\ntitle: Avoid Layout Thrashing\nimpact: MEDIUM\nimpactDescription: prevents forced synchronous layouts and reduces performance bottlenecks\ntags: javascript, dom, css, performance, reflow, layout-thrashing\n---\n\n## Avoid Layout Thrashing\n\nAvoid interleaving style writes with layout reads. When you read a layout property (like `offsetWidth`, `getBoundingClientRect()`, or `getComputedStyle()`) between style changes, the browser is forced to trigger a synchronous reflow.\n\n**This is OK (browser batches style changes):**\n```typescript\nfunction updateElementStyles(element: HTMLElement) {\n  // Each line invalidates style, but browser batches the recalculation\n  element.style.width = '100px'\n  element.style.height = '200px'\n  element.style.backgroundColor = 'blue'\n  element.style.border = '1px solid black'\n}\n```\n\n**Incorrect (interleaved reads and writes force reflows):**\n```typescript\nfunction layoutThrashing(element: HTMLElement) {\n  element.style.width = '100px'\n  const width = element.offsetWidth  // Forces reflow\n  element.style.height = '200px'\n  const height = element.offsetHeight  // Forces another reflow\n}\n```\n\n**Correct (batch writes, then read once):**\n```typescript\nfunction updateElementStyles(element: HTMLElement) {\n  // Batch all writes together\n  element.style.width = '100px'\n  element.style.height = '200px'\n  element.style.backgroundColor = 'blue'\n  element.style.border = '1px solid black'\n  \n  // Read after all writes are done (single reflow)\n  const { width, height } = element.getBoundingClientRect()\n}\n```\n\n**Correct (batch reads, then writes):**\n```typescript\nfunction avoidThrashing(element: HTMLElement) {\n  // Read phase - all layout queries first\n  const rect1 = element.getBoundingClientRect()\n  const offsetWidth = element.offsetWidth\n  const offsetHeight = element.offsetHeight\n  \n  // Write phase - all style changes after\n  element.style.width = '100px'\n  element.style.height = '200px'\n}\n```\n\n**Better: use CSS classes**\n```css\n.highlighted-box {\n  width: 100px;\n  height: 200px;\n  background-color: blue;\n  border: 1px solid black;\n}\n```\n```typescript\nfunction updateElementStyles(element: HTMLElement) {\n  element.classList.add('highlighted-box')\n  \n  const { width, height } = element.getBoundingClientRect()\n}\n```\n\n**React example:**\n```tsx\n// Incorrect: interleaving style changes with layout queries\nfunction Box({ isHighlighted }: { isHighlighted: boolean }) {\n  const ref = useRef<HTMLDivElement>(null)\n  \n  useEffect(() => {\n    if (ref.current && isHighlighted) {\n      ref.current.style.width = '100px'\n      const width = ref.current.offsetWidth // Forces layout\n      ref.current.style.height = '200px'\n    }\n  }, [isHighlighted])\n  \n  return <div ref={ref}>Content</div>\n}\n\n// Correct: toggle class\nfunction Box({ isHighlighted }: { isHighlighted: boolean }) {\n  return (\n    <div className={isHighlighted ? 'highlighted-box' : ''}>\n      Content\n    </div>\n  )\n}\n```\n\nPrefer CSS classes over inline styles when possible. CSS files are cached by the browser, and classes provide better separation of concerns and are easier to maintain.\n\nSee [this gist](https://gist.github.com/paulirish/5d52fb081b3570c81e3a) and [CSS Triggers](https://csstriggers.com/) for more information on layout-forcing operations.\n",".agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md":"---\ntitle: Cache Repeated Function Calls\nimpact: MEDIUM\nimpactDescription: avoid redundant computation\ntags: javascript, cache, memoization, performance\n---\n\n## Cache Repeated Function Calls\n\nUse a module-level Map to cache function results when the same function is called repeatedly with the same inputs during render.\n\n**Incorrect (redundant computation):**\n\n```typescript\nfunction ProjectList({ projects }: { projects: Project[] }) {\n  return (\n    <div>\n      {projects.map(project => {\n        // slugify() called 100+ times for same project names\n        const slug = slugify(project.name)\n        \n        return <ProjectCard key={project.id} slug={slug} />\n      })}\n    </div>\n  )\n}\n```\n\n**Correct (cached results):**\n\n```typescript\n// Module-level cache\nconst slugifyCache = new Map<string, string>()\n\nfunction cachedSlugify(text: string): string {\n  if (slugifyCache.has(text)) {\n    return slugifyCache.get(text)!\n  }\n  const result = slugify(text)\n  slugifyCache.set(text, result)\n  return result\n}\n\nfunction ProjectList({ projects }: { projects: Project[] }) {\n  return (\n    <div>\n      {projects.map(project => {\n        // Computed only once per unique project name\n        const slug = cachedSlugify(project.name)\n        \n        return <ProjectCard key={project.id} slug={slug} />\n      })}\n    </div>\n  )\n}\n```\n\n**Simpler pattern for single-value functions:**\n\n```typescript\nlet isLoggedInCache: boolean | null = null\n\nfunction isLoggedIn(): boolean {\n  if (isLoggedInCache !== null) {\n    return isLoggedInCache\n  }\n  \n  isLoggedInCache = document.cookie.includes('auth=')\n  return isLoggedInCache\n}\n\n// Clear cache when auth changes\nfunction onAuthChange() {\n  isLoggedInCache = null\n}\n```\n\nUse a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.\n\nReference: [How we made the Vercel Dashboard twice as fast](https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast)\n",".agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md":"---\ntitle: Cache Property Access in Loops\nimpact: LOW-MEDIUM\nimpactDescription: reduces lookups\ntags: javascript, loops, optimization, caching\n---\n\n## Cache Property Access in Loops\n\nCache object property lookups in hot paths.\n\n**Incorrect (3 lookups × N iterations):**\n\n```typescript\nfor (let i = 0; i < arr.length; i++) {\n  process(obj.config.settings.value)\n}\n```\n\n**Correct (1 lookup total):**\n\n```typescript\nconst value = obj.config.settings.value\nconst len = arr.length\nfor (let i = 0; i < len; i++) {\n  process(value)\n}\n```\n",".agents/skills/vercel-react-best-practices/rules/js-cache-storage.md":"---\ntitle: Cache Storage API Calls\nimpact: LOW-MEDIUM\nimpactDescription: reduces expensive I/O\ntags: javascript, localStorage, storage, caching, performance\n---\n\n## Cache Storage API Calls\n\n`localStorage`, `sessionStorage`, and `document.cookie` are synchronous and expensive. Cache reads in memory.\n\n**Incorrect (reads storage on every call):**\n\n```typescript\nfunction getTheme() {\n  return localStorage.getItem('theme') ?? 'light'\n}\n// Called 10 times = 10 storage reads\n```\n\n**Correct (Map cache):**\n\n```typescript\nconst storageCache = new Map<string, string | null>()\n\nfunction getLocalStorage(key: string) {\n  if (!storageCache.has(key)) {\n    storageCache.set(key, localStorage.getItem(key))\n  }\n  return storageCache.get(key)\n}\n\nfunction setLocalStorage(key: string, value: string) {\n  localStorage.setItem(key, value)\n  storageCache.set(key, value)  // keep cache in sync\n}\n```\n\nUse a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.\n\n**Cookie caching:**\n\n```typescript\nlet cookieCache: Record<string, string> | null = null\n\nfunction getCookie(name: string) {\n  if (!cookieCache) {\n    cookieCache = Object.fromEntries(\n      document.cookie.split('; ').map(c => c.split('='))\n    )\n  }\n  return cookieCache[name]\n}\n```\n\n**Important (invalidate on external changes):**\n\nIf storage can change externally (another tab, server-set cookies), invalidate cache:\n\n```typescript\nwindow.addEventListener('storage', (e) => {\n  if (e.key) storageCache.delete(e.key)\n})\n\ndocument.addEventListener('visibilitychange', () => {\n  if (document.visibilityState === 'visible') {\n    storageCache.clear()\n  }\n})\n```\n",".agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md":"---\ntitle: Combine Multiple Array Iterations\nimpact: LOW-MEDIUM\nimpactDescription: reduces iterations\ntags: javascript, arrays, loops, performance\n---\n\n## Combine Multiple Array Iterations\n\nMultiple `.filter()` or `.map()` calls iterate the array multiple times. Combine into one loop.\n\n**Incorrect (3 iterations):**\n\n```typescript\nconst admins = users.filter(u => u.isAdmin)\nconst testers = users.filter(u => u.isTester)\nconst inactive = users.filter(u => !u.isActive)\n```\n\n**Correct (1 iteration):**\n\n```typescript\nconst admins: User[] = []\nconst testers: User[] = []\nconst inactive: User[] = []\n\nfor (const user of users) {\n  if (user.isAdmin) admins.push(user)\n  if (user.isTester) testers.push(user)\n  if (!user.isActive) inactive.push(user)\n}\n```\n",".agents/skills/vercel-react-best-practices/rules/js-early-exit.md":"---\ntitle: Early Return from Functions\nimpact: LOW-MEDIUM\nimpactDescription: avoids unnecessary computation\ntags: javascript, functions, optimization, early-return\n---\n\n## Early Return from Functions\n\nReturn early when result is determined to skip unnecessary processing.\n\n**Incorrect (processes all items even after finding answer):**\n\n```typescript\nfunction validateUsers(users: User[]) {\n  let hasError = false\n  let errorMessage = ''\n  \n  for (const user of users) {\n    if (!user.email) {\n      hasError = true\n      errorMessage = 'Email required'\n    }\n    if (!user.name) {\n      hasError = true\n      errorMessage = 'Name required'\n    }\n    // Continues checking all users even after error found\n  }\n  \n  return hasError ? { valid: false, error: errorMessage } : { valid: true }\n}\n```\n\n**Correct (returns immediately on first error):**\n\n```typescript\nfunction validateUsers(users: User[]) {\n  for (const user of users) {\n    if (!user.email) {\n      return { valid: false, error: 'Email required' }\n    }\n    if (!user.name) {\n      return { valid: false, error: 'Name required' }\n    }\n  }\n\n  return { valid: true }\n}\n```\n",".agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md":"---\ntitle: Hoist RegExp Creation\nimpact: LOW-MEDIUM\nimpactDescription: avoids recreation\ntags: javascript, regexp, optimization, memoization\n---\n\n## Hoist RegExp Creation\n\nDon't create RegExp inside render. Hoist to module scope or memoize with `useMemo()`.\n\n**Incorrect (new RegExp every render):**\n\n```tsx\nfunction Highlighter({ text, query }: Props) {\n  const regex = new RegExp(`(${query})`, 'gi')\n  const parts = text.split(regex)\n  return <>{parts.map((part, i) => ...)}</>\n}\n```\n\n**Correct (memoize or hoist):**\n\n```tsx\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\nfunction Highlighter({ text, query }: Props) {\n  const regex = useMemo(\n    () => new RegExp(`(${escapeRegex(query)})`, 'gi'),\n    [query]\n  )\n  const parts = text.split(regex)\n  return <>{parts.map((part, i) => ...)}</>\n}\n```\n\n**Warning (global regex has mutable state):**\n\nGlobal regex (`/g`) has mutable `lastIndex` state:\n\n```typescript\nconst regex = /foo/g\nregex.test('foo')  // true, lastIndex = 3\nregex.test('foo')  // false, lastIndex = 0\n```\n",".agents/skills/vercel-react-best-practices/rules/js-index-maps.md":"---\ntitle: Build Index Maps for Repeated Lookups\nimpact: LOW-MEDIUM\nimpactDescription: 1M ops to 2K ops\ntags: javascript, map, indexing, optimization, performance\n---\n\n## Build Index Maps for Repeated Lookups\n\nMultiple `.find()` calls by the same key should use a Map.\n\n**Incorrect (O(n) per lookup):**\n\n```typescript\nfunction processOrders(orders: Order[], users: User[]) {\n  return orders.map(order => ({\n    ...order,\n    user: users.find(u => u.id === order.userId)\n  }))\n}\n```\n\n**Correct (O(1) per lookup):**\n\n```typescript\nfunction processOrders(orders: Order[], users: User[]) {\n  const userById = new Map(users.map(u => [u.id, u]))\n\n  return orders.map(order => ({\n    ...order,\n    user: userById.get(order.userId)\n  }))\n}\n```\n\nBuild map once (O(n)), then all lookups are O(1).\nFor 1000 orders × 1000 users: 1M ops → 2K ops.\n",".agents/skills/vercel-react-best-practices/rules/js-length-check-first.md":"---\ntitle: Early Length Check for Array Comparisons\nimpact: MEDIUM-HIGH\nimpactDescription: avoids expensive operations when lengths differ\ntags: javascript, arrays, performance, optimization, comparison\n---\n\n## Early Length Check for Array Comparisons\n\nWhen comparing arrays with expensive operations (sorting, deep equality, serialization), check lengths first. If lengths differ, the arrays cannot be equal.\n\nIn real-world applications, this optimization is especially valuable when the comparison runs in hot paths (event handlers, render loops).\n\n**Incorrect (always runs expensive comparison):**\n\n```typescript\nfunction hasChanges(current: string[], original: string[]) {\n  // Always sorts and joins, even when lengths differ\n  return current.sort().join() !== original.sort().join()\n}\n```\n\nTwo O(n log n) sorts run even when `current.length` is 5 and `original.length` is 100. There is also overhead of joining the arrays and comparing the strings.\n\n**Correct (O(1) length check first):**\n\n```typescript\nfunction hasChanges(current: string[], original: string[]) {\n  // Early return if lengths differ\n  if (current.length !== original.length) {\n    return true\n  }\n  // Only sort when lengths match\n  const currentSorted = current.toSorted()\n  const originalSorted = original.toSorted()\n  for (let i = 0; i < currentSorted.length; i++) {\n    if (currentSorted[i] !== originalSorted[i]) {\n      return true\n    }\n  }\n  return false\n}\n```\n\nThis new approach is more efficient because:\n- It avoids the overhead of sorting and joining the arrays when lengths differ\n- It avoids consuming memory for the joined strings (especially important for large arrays)\n- It avoids mutating the original arrays\n- It returns early when a difference is found\n",".agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md":"---\ntitle: Use Loop for Min/Max Instead of Sort\nimpact: LOW\nimpactDescription: O(n) instead of O(n log n)\ntags: javascript, arrays, performance, sorting, algorithms\n---\n\n## Use Loop for Min/Max Instead of Sort\n\nFinding the smallest or largest element only requires a single pass through the array. Sorting is wasteful and slower.\n\n**Incorrect (O(n log n) - sort to find latest):**\n\n```typescript\ninterface Project {\n  id: string\n  name: string\n  updatedAt: number\n}\n\nfunction getLatestProject(projects: Project[]) {\n  const sorted = [...projects].sort((a, b) => b.updatedAt - a.updatedAt)\n  return sorted[0]\n}\n```\n\nSorts the entire array just to find the maximum value.\n\n**Incorrect (O(n log n) - sort for oldest and newest):**\n\n```typescript\nfunction getOldestAndNewest(projects: Project[]) {\n  const sorted = [...projects].sort((a, b) => a.updatedAt - b.updatedAt)\n  return { oldest: sorted[0], newest: sorted[sorted.length - 1] }\n}\n```\n\nStill sorts unnecessarily when only min/max are needed.\n\n**Correct (O(n) - single loop):**\n\n```typescript\nfunction getLatestProject(projects: Project[]) {\n  if (projects.length === 0) return null\n  \n  let latest = projects[0]\n  \n  for (let i = 1; i < projects.length; i++) {\n    if (projects[i].updatedAt > latest.updatedAt) {\n      latest = projects[i]\n    }\n  }\n  \n  return latest\n}\n\nfunction getOldestAndNewest(projects: Project[]) {\n  if (projects.length === 0) return { oldest: null, newest: null }\n  \n  let oldest = projects[0]\n  let newest = projects[0]\n  \n  for (let i = 1; i < projects.length; i++) {\n    if (projects[i].updatedAt < oldest.updatedAt) oldest = projects[i]\n    if (projects[i].updatedAt > newest.updatedAt) newest = projects[i]\n  }\n  \n  return { oldest, newest }\n}\n```\n\nSingle pass through the array, no copying, no sorting.\n\n**Alternative (Math.min/Math.max for small arrays):**\n\n```typescript\nconst numbers = [5, 2, 8, 1, 9]\nconst min = Math.min(...numbers)\nconst max = Math.max(...numbers)\n```\n\nThis works for small arrays, but can be slower or just throw an error for very large arrays due to spread operator limitations. Maximal array length is approximately 124000 in Chrome 143 and 638000 in Safari 18; exact numbers may vary - see [the fiddle](https://jsfiddle.net/qw1jabsx/4/). Use the loop approach for reliability.\n",".agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-set-map-lookups.md -->",".agents/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-tosorted-immutable.md -->",".agents/skills/vercel-react-best-practices/rules/rendering-activity.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-activity.md -->",".agents/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-animate-svg-wrapper.md -->",".agents/skills/vercel-react-best-practices/rules/rendering-conditional-render.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-conditional-render.md -->",".agents/skills/vercel-react-best-practices/rules/rendering-content-visibility.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-content-visibility.md -->",".agents/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-hoist-jsx.md -->",".agents/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-hydration-no-flicker.md -->",".agents/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-hydration-suppress-warning.md -->",".agents/skills/vercel-react-best-practices/rules/rendering-svg-precision.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-svg-precision.md -->",".agents/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-usetransition-loading.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-defer-reads.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-defer-reads.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-dependencies.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-dependencies.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-derived-state-no-effect.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-derived-state.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-derived-state.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-functional-setstate.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-lazy-state-init.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-memo-with-default-value.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-memo.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-memo.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-move-effect-to-event.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-simple-expression-in-memo.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-transitions.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-transitions.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-use-ref-transient-values.md -->",".agents/skills/vercel-react-best-practices/rules/server-after-nonblocking.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-after-nonblocking.md -->",".agents/skills/vercel-react-best-practices/rules/server-auth-actions.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-auth-actions.md -->",".agents/skills/vercel-react-best-practices/rules/server-cache-lru.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-cache-lru.md -->",".agents/skills/vercel-react-best-practices/rules/server-cache-react.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-cache-react.md -->",".agents/skills/vercel-react-best-practices/rules/server-dedup-props.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-dedup-props.md -->",".agents/skills/vercel-react-best-practices/rules/server-hoist-static-io.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-hoist-static-io.md -->",".agents/skills/vercel-react-best-practices/rules/server-parallel-fetching.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-parallel-fetching.md -->",".agents/skills/vercel-react-best-practices/rules/server-serialization.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-serialization.md -->",".agents/skills/web-animation-design/PRACTICAL-TIPS.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fweb-animation-design%2FPRACTICAL-TIPS.md -->",".agents/skills/web-animation-design/SKILL.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fweb-animation-design%2FSKILL.md -->",".agents/skills/workflow/SKILL.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fworkflow%2FSKILL.md -->","AGENTS.md":"# AGENTS.md\n\nThis file provides guidance for AI coding agents working in this repository.\n\n**This is a living document.** When you make a mistake or learn something new about this codebase, add it to [Lessons Learned](docs/agents/lessons-learned.md).\n\n## Quick Links\n\n- [Architecture & Workspace Structure](docs/agents/architecture.md)\n- [Code Style & Patterns](docs/agents/code-style.md)\n- [Lessons Learned](docs/agents/lessons-learned.md)\n\n## Authentication\n\nAuthentication uses [Better Auth](https://www.better-auth.com/) with Vercel OAuth (sign-in) and GitHub OAuth (repo access). Config lives in `apps/web/lib/auth/config.ts`. Sessions are managed by better-auth's built-in session system — there is no manual JWE/encryption layer.\n\nKey env vars: `BETTER_AUTH_SECRET` (session signing), `NEXT_PUBLIC_VERCEL_APP_CLIENT_ID` + `VERCEL_APP_CLIENT_SECRET` (Vercel OAuth), plus GitHub App credentials for repo access. See `apps/web/.env.example` for the full list.\n\n## Database & Migrations\n\nSchema lives in `apps/web/lib/db/schema.ts`. Migrations are managed by Drizzle Kit.\n\n**After modifying `schema.ts`, always generate a migration:**\n\n```bash\npnpm --dir apps/web db:generate   # Creates a new .sql migration file\n```\n\nCommit the generated `.sql` file alongside the schema change. **Do not use `db:push`** except for local throwaway databases.\n\nMigrations run automatically during `pnpm build` (via `lib/db/migrate.ts`), so every Vercel deploy — both preview and production — applies pending migrations to its own database.\n\n### Environment isolation\n\nNeon database branching is enabled in the Vercel project settings. Every preview deployment automatically gets its own isolated database branch forked from production. This means preview deployments never read or write production data. Production deployments use the main Neon database.\n\n## Commands\n\n```bash\n# Development\npnpm web            # Run web app\n\n# Quality checks (REQUIRED after making any changes)\npnpm run ci                             # Required: run format check, lint, typecheck, and tests\nturbo typecheck                            # Type check all packages\n\n# Linting and formatting (Ultracite - oxlint + oxfmt, run from root)\npnpm check                              # Lint and format check all files\npnpm fix                                # Lint fix and format all files\n\n# Filter by package (use --filter)\nturbo typecheck --filter=web # Type check web app only\n\n# Testing\nbun test                                              # Run all tests\nbun test path/to/file.test.ts                         # Run single test file\nbun test --watch                                      # Watch mode\npnpm test:verbose                                  # Run tests with JUnit reporter streamed to stdout (useful in non-interactive shells)\npnpm test:verbose path/to/file.test.ts             # Same verbose output for a single test file\n```\n\n**CI/script execution rules:**\n\n- Run project checks through package scripts (for example `pnpm run ci`, `pnpm --dir apps/web db:check`).\n- Prefer `pnpm <script>` over invoking tool binaries directly (`pnpm exec`, `tsc`, `eslint`, etc.) so local runs match CI behavior.\n\n## Git Commands\n\n- **Branch sync preference:** When bringing in `origin/main`, prefer a normal merge (`git fetch origin main` then `git merge origin/main`) instead of rebasing, unless explicitly requested otherwise.\n\n**Quote paths with special characters**: File paths containing brackets (like Next.js dynamic routes `[id]`, `[slug]`) are interpreted as glob patterns by zsh. Always quote these paths in git commands:\n\n```bash\n# Wrong - zsh interprets [id] as a glob pattern\ngit add apps/web/app/tasks/[id]/page.tsx\n# Error: no matches found: apps/web/app/tasks/[id]/page.tsx\n\n# Correct - quote the path\ngit add \"apps/web/app/tasks/[id]/page.tsx\"\n```\n\n## Architecture (Summary)\n\n```\nWeb -> Agent (packages/agent) -> Sandbox (packages/sandbox)\n```\n\nSee [Architecture & Workspace Structure](docs/agents/architecture.md) for details.\n\n## File Organization & Separation of Concerns\n\n- Do **not** append new functionality to the bottom of an existing file by default.\n- Before adding code, decide whether the behavior is a separate concern that should live in its own file.\n- Prefer creating a new colocated file for distinct concerns (components, hooks, utilities, schemas, data-access helpers, etc.).\n- If a file is already large or handling multiple responsibilities, extract the new logic (and related helpers/types) into focused modules and import them.\n- For large page/view/client components, default to adding new feature behavior in colocated hooks and colocated child components instead of growing the main file.\n- If a change introduces a distinct cluster of state, effects, handlers, API calls, or derived UI labels for one feature, treat that as a strong signal to extract it.\n- Keep each file focused on one primary responsibility; avoid mixing unrelated UI, business logic, and data-access code in the same file.\n\n## Code Style (Summary)\n\n- **pnpm exclusively for dependency management**; use Node 24 for utility scripts and Bun for tests\n- **Files**: kebab-case, **Types**: PascalCase, **Functions**: camelCase\n- **Never use `any`** -- use `unknown` and narrow with type guards\n- **No `.js` extensions** in imports\n- **Ultracite** (oxlint + oxfmt) for linting and formatting (double quotes, 2-space indent)\n- **Zod** schemas for validation, derive types with `z.infer`\n\nSee [Code Style & Patterns](docs/agents/code-style.md) for full conventions, tool implementation patterns, and dependency patterns.\n","CLAUDE.md":"AGENTS.md","docs/agents/architecture.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=docs%2Fagents%2Farchitecture.md -->","docs/agents/code-style.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=docs%2Fagents%2Fcode-style.md -->","docs/agents/lessons-learned.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=docs%2Fagents%2Flessons-learned.md -->","docs/agents/react-best-practices-audit.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=docs%2Fagents%2Freact-best-practices-audit.md -->","packages/agent/docs/approval-system.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=packages%2Fagent%2Fdocs%2Fapproval-system.md -->"},"files":{".agents/skills/agent-browser/SKILL.md":"---\nname: agent-browser\ndescription: Automates browser interactions for web testing, form filling, screenshots, and data extraction. Use when the user needs to navigate websites, interact with web pages, fill forms, take screenshots, test web applications, or extract information from web pages.\nallowed-tools: Bash(agent-browser:*)\n---\n\n# Browser Automation with agent-browser\n\n## Quick start\n\n```bash\nagent-browser open <url>        # Navigate to page\nagent-browser snapshot -i       # Get interactive elements with refs\nagent-browser click @e1         # Click element by ref\nagent-browser fill @e2 \"text\"   # Fill input by ref\nagent-browser close             # Close browser\n```\n\n## Core workflow\n\n1. Navigate: `agent-browser open <url>`\n2. Snapshot: `agent-browser snapshot -i` (returns elements with refs like `@e1`, `@e2`)\n3. Interact using refs from the snapshot\n4. Re-snapshot after navigation or significant DOM changes\n\n## Commands\n\n### Navigation\n\n```bash\nagent-browser open <url>      # Navigate to URL (aliases: goto, navigate)\n                              # Supports: https://, http://, file://, about:, data://\n                              # Auto-prepends https:// if no protocol given\nagent-browser back            # Go back\nagent-browser forward         # Go forward\nagent-browser reload          # Reload page\nagent-browser close           # Close browser (aliases: quit, exit)\nagent-browser connect 9222    # Connect to browser via CDP port\n```\n\n### Snapshot (page analysis)\n\n```bash\nagent-browser snapshot            # Full accessibility tree\nagent-browser snapshot -i         # Interactive elements only (recommended)\nagent-browser snapshot -c         # Compact output\nagent-browser snapshot -d 3       # Limit depth to 3\nagent-browser snapshot -s \"#main\" # Scope to CSS selector\n```\n\n### Interactions (use @refs from snapshot)\n\n```bash\nagent-browser click @e1           # Click\nagent-browser dblclick @e1        # Double-click\nagent-browser focus @e1           # Focus element\nagent-browser fill @e2 \"text\"     # Clear and type\nagent-browser type @e2 \"text\"     # Type without clearing\nagent-browser press Enter         # Press key (alias: key)\nagent-browser press Control+a     # Key combination\nagent-browser keydown Shift       # Hold key down\nagent-browser keyup Shift         # Release key\nagent-browser hover @e1           # Hover\nagent-browser check @e1           # Check checkbox\nagent-browser uncheck @e1         # Uncheck checkbox\nagent-browser select @e1 \"value\"  # Select dropdown option\nagent-browser select @e1 \"a\" \"b\"  # Select multiple options\nagent-browser scroll down 500     # Scroll page (default: down 300px)\nagent-browser scrollintoview @e1  # Scroll element into view (alias: scrollinto)\nagent-browser drag @e1 @e2        # Drag and drop\nagent-browser upload @e1 file.pdf # Upload files\n```\n\n### Get information\n\n```bash\nagent-browser get text @e1        # Get element text\nagent-browser get html @e1        # Get innerHTML\nagent-browser get value @e1       # Get input value\nagent-browser get attr @e1 href   # Get attribute\nagent-browser get title           # Get page title\nagent-browser get url             # Get current URL\nagent-browser get count \".item\"   # Count matching elements\nagent-browser get box @e1         # Get bounding box\nagent-browser get styles @e1      # Get computed styles (font, color, bg, etc.)\n```\n\n### Check state\n\n```bash\nagent-browser is visible @e1      # Check if visible\nagent-browser is enabled @e1      # Check if enabled\nagent-browser is checked @e1      # Check if checked\n```\n\n### Screenshots & PDF\n\n```bash\nagent-browser screenshot          # Save to a temporary directory\nagent-browser screenshot path.png # Save to a specific path\nagent-browser screenshot --full   # Full page\nagent-browser pdf output.pdf      # Save as PDF\n```\n\n### Video recording\n\n```bash\nagent-browser record start ./demo.webm    # Start recording (uses current URL + state)\nagent-browser click @e1                   # Perform actions\nagent-browser record stop                 # Stop and save video\nagent-browser record restart ./take2.webm # Stop current + start new recording\n```\n\nRecording creates a fresh context but preserves cookies/storage from your session. If no URL is provided, it\nautomatically returns to your current page. For smooth demos, explore first, then start recording.\n\n### Wait\n\n```bash\nagent-browser wait @e1                     # Wait for element\nagent-browser wait 2000                    # Wait milliseconds\nagent-browser wait --text \"Success\"        # Wait for text (or -t)\nagent-browser wait --url \"**/dashboard\"    # Wait for URL pattern (or -u)\nagent-browser wait --load networkidle      # Wait for network idle (or -l)\nagent-browser wait --fn \"window.ready\"     # Wait for JS condition (or -f)\n```\n\n### Mouse control\n\n```bash\nagent-browser mouse move 100 200      # Move mouse\nagent-browser mouse down left         # Press button\nagent-browser mouse up left           # Release button\nagent-browser mouse wheel 100         # Scroll wheel\n```\n\n### Semantic locators (alternative to refs)\n\n```bash\nagent-browser find role button click --name \"Submit\"\nagent-browser find text \"Sign In\" click\nagent-browser find text \"Sign In\" click --exact      # Exact match only\nagent-browser find label \"Email\" fill \"user@test.com\"\nagent-browser find placeholder \"Search\" type \"query\"\nagent-browser find alt \"Logo\" click\nagent-browser find title \"Close\" click\nagent-browser find testid \"submit-btn\" click\nagent-browser find first \".item\" click\nagent-browser find last \".item\" click\nagent-browser find nth 2 \"a\" hover\n```\n\n### Browser settings\n\n```bash\nagent-browser set viewport 1920 1080          # Set viewport size\nagent-browser set device \"iPhone 14\"          # Emulate device\nagent-browser set geo 37.7749 -122.4194       # Set geolocation (alias: geolocation)\nagent-browser set offline on                  # Toggle offline mode\nagent-browser set headers '{\"X-Key\":\"v\"}'     # Extra HTTP headers\nagent-browser set credentials user pass       # HTTP basic auth (alias: auth)\nagent-browser set media dark                  # Emulate color scheme\nagent-browser set media light reduced-motion  # Light mode + reduced motion\n```\n\n### Cookies & Storage\n\n```bash\nagent-browser cookies                     # Get all cookies\nagent-browser cookies set name value      # Set cookie\nagent-browser cookies clear               # Clear cookies\nagent-browser storage local               # Get all localStorage\nagent-browser storage local key           # Get specific key\nagent-browser storage local set k v       # Set value\nagent-browser storage local clear         # Clear all\n```\n\n### Network\n\n```bash\nagent-browser network route <url>              # Intercept requests\nagent-browser network route <url> --abort      # Block requests\nagent-browser network route <url> --body '{}'  # Mock response\nagent-browser network unroute [url]            # Remove routes\nagent-browser network requests                 # View tracked requests\nagent-browser network requests --filter api    # Filter requests\n```\n\n### Tabs & Windows\n\n```bash\nagent-browser tab                 # List tabs\nagent-browser tab new [url]       # New tab\nagent-browser tab 2               # Switch to tab by index\nagent-browser tab close           # Close current tab\nagent-browser tab close 2         # Close tab by index\nagent-browser window new          # New window\n```\n\n### Frames\n\n```bash\nagent-browser frame \"#iframe\"     # Switch to iframe\nagent-browser frame main          # Back to main frame\n```\n\n### Dialogs\n\n```bash\nagent-browser dialog accept [text]  # Accept dialog\nagent-browser dialog dismiss        # Dismiss dialog\n```\n\n### JavaScript\n\n```bash\nagent-browser eval \"document.title\"   # Run JavaScript\n```\n\n## Global options\n\n```bash\nagent-browser --session <name> ...    # Isolated browser session\nagent-browser --json ...              # JSON output for parsing\nagent-browser --headed ...            # Show browser window (not headless)\nagent-browser --full ...              # Full page screenshot (-f)\nagent-browser --cdp <port> ...        # Connect via Chrome DevTools Protocol\nagent-browser -p <provider> ...       # Cloud browser provider (--provider)\nagent-browser --proxy <url> ...       # Use proxy server\nagent-browser --headers <json> ...    # HTTP headers scoped to URL's origin\nagent-browser --executable-path <p>   # Custom browser executable\nagent-browser --extension <path> ...  # Load browser extension (repeatable)\nagent-browser --help                  # Show help (-h)\nagent-browser --version               # Show version (-V)\nagent-browser <command> --help        # Show detailed help for a command\n```\n\n### Proxy support\n\n```bash\nagent-browser --proxy http://proxy.com:8080 open example.com\nagent-browser --proxy http://user:pass@proxy.com:8080 open example.com\nagent-browser --proxy socks5://proxy.com:1080 open example.com\n```\n\n## Environment variables\n\n```bash\nAGENT_BROWSER_SESSION=\"mysession\"            # Default session name\nAGENT_BROWSER_EXECUTABLE_PATH=\"/path/chrome\" # Custom browser path\nAGENT_BROWSER_EXTENSIONS=\"/ext1,/ext2\"       # Comma-separated extension paths\nAGENT_BROWSER_PROVIDER=\"your-cloud-browser-provider\"  # Cloud browser provider (select browseruse or browserbase)\nAGENT_BROWSER_STREAM_PORT=\"9223\"             # WebSocket streaming port\nAGENT_BROWSER_HOME=\"/path/to/agent-browser\"  # Custom install location (for daemon.js)\n```\n\n## Example: Form submission\n\n```bash\nagent-browser open https://example.com/form\nagent-browser snapshot -i\n# Output shows: textbox \"Email\" [ref=e1], textbox \"Password\" [ref=e2], button \"Submit\" [ref=e3]\n\nagent-browser fill @e1 \"user@example.com\"\nagent-browser fill @e2 \"password123\"\nagent-browser click @e3\nagent-browser wait --load networkidle\nagent-browser snapshot -i  # Check result\n```\n\n## Example: Authentication with saved state\n\n```bash\n# Login once\nagent-browser open https://app.example.com/login\nagent-browser snapshot -i\nagent-browser fill @e1 \"username\"\nagent-browser fill @e2 \"password\"\nagent-browser click @e3\nagent-browser wait --url \"**/dashboard\"\nagent-browser state save auth.json\n\n# Later sessions: load saved state\nagent-browser state load auth.json\nagent-browser open https://app.example.com/dashboard\n```\n\n## Sessions (parallel browsers)\n\n```bash\nagent-browser --session test1 open site-a.com\nagent-browser --session test2 open site-b.com\nagent-browser session list\n```\n\n## JSON output (for parsing)\n\nAdd `--json` for machine-readable output:\n\n```bash\nagent-browser snapshot -i --json\nagent-browser get text @e1 --json\n```\n\n## Debugging\n\n```bash\nagent-browser --headed open example.com   # Show browser window\nagent-browser --cdp 9222 snapshot         # Connect via CDP port\nagent-browser connect 9222                # Alternative: connect command\nagent-browser console                     # View console messages\nagent-browser console --clear             # Clear console\nagent-browser errors                      # View page errors\nagent-browser errors --clear              # Clear errors\nagent-browser highlight @e1               # Highlight element\nagent-browser trace start                 # Start recording trace\nagent-browser trace stop trace.zip        # Stop and save trace\nagent-browser record start ./debug.webm   # Record video from current page\nagent-browser record stop                 # Save recording\n```\n\n## Deep-dive documentation\n\nFor detailed patterns and best practices, see:\n\n| Reference | Description |\n|-----------|-------------|\n| [references/snapshot-refs.md](references/snapshot-refs.md) | Ref lifecycle, invalidation rules, troubleshooting |\n| [references/session-management.md](references/session-management.md) | Parallel sessions, state persistence, concurrent scraping |\n| [references/authentication.md](references/authentication.md) | Login flows, OAuth, 2FA handling, state reuse |\n| [references/video-recording.md](references/video-recording.md) | Recording workflows for debugging and documentation |\n| [references/proxy-support.md](references/proxy-support.md) | Proxy configuration, geo-testing, rotating proxies |\n\n## Ready-to-use templates\n\nExecutable workflow scripts for common patterns:\n\n| Template | Description |\n|----------|-------------|\n| [templates/form-automation.sh](templates/form-automation.sh) | Form filling with validation |\n| [templates/authenticated-session.sh](templates/authenticated-session.sh) | Login once, reuse state |\n| [templates/capture-workflow.sh](templates/capture-workflow.sh) | Content extraction with screenshots |\n\nUsage:\n```bash\n./templates/form-automation.sh https://example.com/form\n./templates/authenticated-session.sh https://app.example.com/login\n./templates/capture-workflow.sh https://example.com ./output\n```\n\n## HTTPS Certificate Errors\n\nFor sites with self-signed or invalid certificates:\n```bash\nagent-browser open https://localhost:8443 --ignore-https-errors\n```\n",".agents/skills/agent-browser/references/authentication.md":"# Authentication Patterns\n\nPatterns for handling login flows, session persistence, and authenticated browsing.\n\n## Basic Login Flow\n\n```bash\n# Navigate to login page\nagent-browser open https://app.example.com/login\nagent-browser wait --load networkidle\n\n# Get form elements\nagent-browser snapshot -i\n# Output: @e1 [input type=\"email\"], @e2 [input type=\"password\"], @e3 [button] \"Sign In\"\n\n# Fill credentials\nagent-browser fill @e1 \"user@example.com\"\nagent-browser fill @e2 \"password123\"\n\n# Submit\nagent-browser click @e3\nagent-browser wait --load networkidle\n\n# Verify login succeeded\nagent-browser get url  # Should be dashboard, not login\n```\n\n## Saving Authentication State\n\nAfter logging in, save state for reuse:\n\n```bash\n# Login first (see above)\nagent-browser open https://app.example.com/login\nagent-browser snapshot -i\nagent-browser fill @e1 \"user@example.com\"\nagent-browser fill @e2 \"password123\"\nagent-browser click @e3\nagent-browser wait --url \"**/dashboard\"\n\n# Save authenticated state\nagent-browser state save ./auth-state.json\n```\n\n## Restoring Authentication\n\nSkip login by loading saved state:\n\n```bash\n# Load saved auth state\nagent-browser state load ./auth-state.json\n\n# Navigate directly to protected page\nagent-browser open https://app.example.com/dashboard\n\n# Verify authenticated\nagent-browser snapshot -i\n```\n\n## OAuth / SSO Flows\n\nFor OAuth redirects:\n\n```bash\n# Start OAuth flow\nagent-browser open https://app.example.com/auth/google\n\n# Handle redirects automatically\nagent-browser wait --url \"**/accounts.google.com**\"\nagent-browser snapshot -i\n\n# Fill Google credentials\nagent-browser fill @e1 \"user@gmail.com\"\nagent-browser click @e2  # Next button\nagent-browser wait 2000\nagent-browser snapshot -i\nagent-browser fill @e3 \"password\"\nagent-browser click @e4  # Sign in\n\n# Wait for redirect back\nagent-browser wait --url \"**/app.example.com**\"\nagent-browser state save ./oauth-state.json\n```\n\n## Two-Factor Authentication\n\nHandle 2FA with manual intervention:\n\n```bash\n# Login with credentials\nagent-browser open https://app.example.com/login --headed  # Show browser\nagent-browser snapshot -i\nagent-browser fill @e1 \"user@example.com\"\nagent-browser fill @e2 \"password123\"\nagent-browser click @e3\n\n# Wait for user to complete 2FA manually\necho \"Complete 2FA in the browser window...\"\nagent-browser wait --url \"**/dashboard\" --timeout 120000\n\n# Save state after 2FA\nagent-browser state save ./2fa-state.json\n```\n\n## HTTP Basic Auth\n\nFor sites using HTTP Basic Authentication:\n\n```bash\n# Set credentials before navigation\nagent-browser set credentials username password\n\n# Navigate to protected resource\nagent-browser open https://protected.example.com/api\n```\n\n## Cookie-Based Auth\n\nManually set authentication cookies:\n\n```bash\n# Set auth cookie\nagent-browser cookies set session_token \"abc123xyz\"\n\n# Navigate to protected page\nagent-browser open https://app.example.com/dashboard\n```\n\n## Token Refresh Handling\n\nFor sessions with expiring tokens:\n\n```bash\n#!/bin/bash\n# Wrapper that handles token refresh\n\nSTATE_FILE=\"./auth-state.json\"\n\n# Try loading existing state\nif [[ -f \"$STATE_FILE\" ]]; then\n    agent-browser state load \"$STATE_FILE\"\n    agent-browser open https://app.example.com/dashboard\n\n    # Check if session is still valid\n    URL=$(agent-browser get url)\n    if [[ \"$URL\" == *\"/login\"* ]]; then\n        echo \"Session expired, re-authenticating...\"\n        # Perform fresh login\n        agent-browser snapshot -i\n        agent-browser fill @e1 \"$USERNAME\"\n        agent-browser fill @e2 \"$PASSWORD\"\n        agent-browser click @e3\n        agent-browser wait --url \"**/dashboard\"\n        agent-browser state save \"$STATE_FILE\"\n    fi\nelse\n    # First-time login\n    agent-browser open https://app.example.com/login\n    # ... login flow ...\nfi\n```\n\n## Security Best Practices\n\n1. **Never commit state files** - They contain session tokens\n   ```bash\n   echo \"*.auth-state.json\" >> .gitignore\n   ```\n\n2. **Use environment variables for credentials**\n   ```bash\n   agent-browser fill @e1 \"$APP_USERNAME\"\n   agent-browser fill @e2 \"$APP_PASSWORD\"\n   ```\n\n3. **Clean up after automation**\n   ```bash\n   agent-browser cookies clear\n   rm -f ./auth-state.json\n   ```\n\n4. **Use short-lived sessions for CI/CD**\n   ```bash\n   # Don't persist state in CI\n   agent-browser open https://app.example.com/login\n   # ... login and perform actions ...\n   agent-browser close  # Session ends, nothing persisted\n   ```\n",".agents/skills/agent-browser/references/proxy-support.md":"# Proxy Support\n\nConfigure proxy servers for browser automation, useful for geo-testing, rate limiting avoidance, and corporate environments.\n\n## Basic Proxy Configuration\n\nSet proxy via environment variable before starting:\n\n```bash\n# HTTP proxy\nexport HTTP_PROXY=\"http://proxy.example.com:8080\"\nagent-browser open https://example.com\n\n# HTTPS proxy\nexport HTTPS_PROXY=\"https://proxy.example.com:8080\"\nagent-browser open https://example.com\n\n# Both\nexport HTTP_PROXY=\"http://proxy.example.com:8080\"\nexport HTTPS_PROXY=\"http://proxy.example.com:8080\"\nagent-browser open https://example.com\n```\n\n## Authenticated Proxy\n\nFor proxies requiring authentication:\n\n```bash\n# Include credentials in URL\nexport HTTP_PROXY=\"http://username:password@proxy.example.com:8080\"\nagent-browser open https://example.com\n```\n\n## SOCKS Proxy\n\n```bash\n# SOCKS5 proxy\nexport ALL_PROXY=\"socks5://proxy.example.com:1080\"\nagent-browser open https://example.com\n\n# SOCKS5 with auth\nexport ALL_PROXY=\"socks5://user:pass@proxy.example.com:1080\"\nagent-browser open https://example.com\n```\n\n## Proxy Bypass\n\nSkip proxy for specific domains:\n\n```bash\n# Bypass proxy for local addresses\nexport NO_PROXY=\"localhost,127.0.0.1,.internal.company.com\"\nagent-browser open https://internal.company.com  # Direct connection\nagent-browser open https://external.com          # Via proxy\n```\n\n## Common Use Cases\n\n### Geo-Location Testing\n\n```bash\n#!/bin/bash\n# Test site from different regions using geo-located proxies\n\nPROXIES=(\n    \"http://us-proxy.example.com:8080\"\n    \"http://eu-proxy.example.com:8080\"\n    \"http://asia-proxy.example.com:8080\"\n)\n\nfor proxy in \"${PROXIES[@]}\"; do\n    export HTTP_PROXY=\"$proxy\"\n    export HTTPS_PROXY=\"$proxy\"\n\n    region=$(echo \"$proxy\" | grep -oP '^\\w+-\\w+')\n    echo \"Testing from: $region\"\n\n    agent-browser --session \"$region\" open https://example.com\n    agent-browser --session \"$region\" screenshot \"./screenshots/$region.png\"\n    agent-browser --session \"$region\" close\ndone\n```\n\n### Rotating Proxies for Scraping\n\n```bash\n#!/bin/bash\n# Rotate through proxy list to avoid rate limiting\n\nPROXY_LIST=(\n    \"http://proxy1.example.com:8080\"\n    \"http://proxy2.example.com:8080\"\n    \"http://proxy3.example.com:8080\"\n)\n\nURLS=(\n    \"https://site.com/page1\"\n    \"https://site.com/page2\"\n    \"https://site.com/page3\"\n)\n\nfor i in \"${!URLS[@]}\"; do\n    proxy_index=$((i % ${#PROXY_LIST[@]}))\n    export HTTP_PROXY=\"${PROXY_LIST[$proxy_index]}\"\n    export HTTPS_PROXY=\"${PROXY_LIST[$proxy_index]}\"\n\n    agent-browser open \"${URLS[$i]}\"\n    agent-browser get text body > \"output-$i.txt\"\n    agent-browser close\n\n    sleep 1  # Polite delay\ndone\n```\n\n### Corporate Network Access\n\n```bash\n#!/bin/bash\n# Access internal sites via corporate proxy\n\nexport HTTP_PROXY=\"http://corpproxy.company.com:8080\"\nexport HTTPS_PROXY=\"http://corpproxy.company.com:8080\"\nexport NO_PROXY=\"localhost,127.0.0.1,.company.com\"\n\n# External sites go through proxy\nagent-browser open https://external-vendor.com\n\n# Internal sites bypass proxy\nagent-browser open https://intranet.company.com\n```\n\n## Verifying Proxy Connection\n\n```bash\n# Check your apparent IP\nagent-browser open https://httpbin.org/ip\nagent-browser get text body\n# Should show proxy's IP, not your real IP\n```\n\n## Troubleshooting\n\n### Proxy Connection Failed\n\n```bash\n# Test proxy connectivity first\ncurl -x http://proxy.example.com:8080 https://httpbin.org/ip\n\n# Check if proxy requires auth\nexport HTTP_PROXY=\"http://user:pass@proxy.example.com:8080\"\n```\n\n### SSL/TLS Errors Through Proxy\n\nSome proxies perform SSL inspection. If you encounter certificate errors:\n\n```bash\n# For testing only - not recommended for production\nagent-browser open https://example.com --ignore-https-errors\n```\n\n### Slow Performance\n\n```bash\n# Use proxy only when necessary\nexport NO_PROXY=\"*.cdn.com,*.static.com\"  # Direct CDN access\n```\n\n## Best Practices\n\n1. **Use environment variables** - Don't hardcode proxy credentials\n2. **Set NO_PROXY appropriately** - Avoid routing local traffic through proxy\n3. **Test proxy before automation** - Verify connectivity with simple requests\n4. **Handle proxy failures gracefully** - Implement retry logic for unstable proxies\n5. **Rotate proxies for large scraping jobs** - Distribute load and avoid bans\n",".agents/skills/agent-browser/references/session-management.md":"# Session Management\n\nRun multiple isolated browser sessions concurrently with state persistence.\n\n## Named Sessions\n\nUse `--session` flag to isolate browser contexts:\n\n```bash\n# Session 1: Authentication flow\nagent-browser --session auth open https://app.example.com/login\n\n# Session 2: Public browsing (separate cookies, storage)\nagent-browser --session public open https://example.com\n\n# Commands are isolated by session\nagent-browser --session auth fill @e1 \"user@example.com\"\nagent-browser --session public get text body\n```\n\n## Session Isolation Properties\n\nEach session has independent:\n- Cookies\n- LocalStorage / SessionStorage\n- IndexedDB\n- Cache\n- Browsing history\n- Open tabs\n\n## Session State Persistence\n\n### Save Session State\n\n```bash\n# Save cookies, storage, and auth state\nagent-browser state save /path/to/auth-state.json\n```\n\n### Load Session State\n\n```bash\n# Restore saved state\nagent-browser state load /path/to/auth-state.json\n\n# Continue with authenticated session\nagent-browser open https://app.example.com/dashboard\n```\n\n### State File Contents\n\n```json\n{\n  \"cookies\": [...],\n  \"localStorage\": {...},\n  \"sessionStorage\": {...},\n  \"origins\": [...]\n}\n```\n\n## Common Patterns\n\n### Authenticated Session Reuse\n\n```bash\n#!/bin/bash\n# Save login state once, reuse many times\n\nSTATE_FILE=\"/tmp/auth-state.json\"\n\n# Check if we have saved state\nif [[ -f \"$STATE_FILE\" ]]; then\n    agent-browser state load \"$STATE_FILE\"\n    agent-browser open https://app.example.com/dashboard\nelse\n    # Perform login\n    agent-browser open https://app.example.com/login\n    agent-browser snapshot -i\n    agent-browser fill @e1 \"$USERNAME\"\n    agent-browser fill @e2 \"$PASSWORD\"\n    agent-browser click @e3\n    agent-browser wait --load networkidle\n\n    # Save for future use\n    agent-browser state save \"$STATE_FILE\"\nfi\n```\n\n### Concurrent Scraping\n\n```bash\n#!/bin/bash\n# Scrape multiple sites concurrently\n\n# Start all sessions\nagent-browser --session site1 open https://site1.com &\nagent-browser --session site2 open https://site2.com &\nagent-browser --session site3 open https://site3.com &\nwait\n\n# Extract from each\nagent-browser --session site1 get text body > site1.txt\nagent-browser --session site2 get text body > site2.txt\nagent-browser --session site3 get text body > site3.txt\n\n# Cleanup\nagent-browser --session site1 close\nagent-browser --session site2 close\nagent-browser --session site3 close\n```\n\n### A/B Testing Sessions\n\n```bash\n# Test different user experiences\nagent-browser --session variant-a open \"https://app.com?variant=a\"\nagent-browser --session variant-b open \"https://app.com?variant=b\"\n\n# Compare\nagent-browser --session variant-a screenshot /tmp/variant-a.png\nagent-browser --session variant-b screenshot /tmp/variant-b.png\n```\n\n## Default Session\n\nWhen `--session` is omitted, commands use the default session:\n\n```bash\n# These use the same default session\nagent-browser open https://example.com\nagent-browser snapshot -i\nagent-browser close  # Closes default session\n```\n\n## Session Cleanup\n\n```bash\n# Close specific session\nagent-browser --session auth close\n\n# List active sessions\nagent-browser session list\n```\n\n## Best Practices\n\n### 1. Name Sessions Semantically\n\n```bash\n# GOOD: Clear purpose\nagent-browser --session github-auth open https://github.com\nagent-browser --session docs-scrape open https://docs.example.com\n\n# AVOID: Generic names\nagent-browser --session s1 open https://github.com\n```\n\n### 2. Always Clean Up\n\n```bash\n# Close sessions when done\nagent-browser --session auth close\nagent-browser --session scrape close\n```\n\n### 3. Handle State Files Securely\n\n```bash\n# Don't commit state files (contain auth tokens!)\necho \"*.auth-state.json\" >> .gitignore\n\n# Delete after use\nrm /tmp/auth-state.json\n```\n\n### 4. Timeout Long Sessions\n\n```bash\n# Set timeout for automated scripts\ntimeout 60 agent-browser --session long-task get text body\n```\n",".agents/skills/agent-browser/references/snapshot-refs.md":"# Snapshot + Refs Workflow\n\nThe core innovation of agent-browser: compact element references that reduce context usage dramatically for AI agents.\n\n## How It Works\n\n### The Problem\nTraditional browser automation sends full DOM to AI agents:\n```\nFull DOM/HTML sent → AI parses → Generates CSS selector → Executes action\n~3000-5000 tokens per interaction\n```\n\n### The Solution\nagent-browser uses compact snapshots with refs:\n```\nCompact snapshot → @refs assigned → Direct ref interaction\n~200-400 tokens per interaction\n```\n\n## The Snapshot Command\n\n```bash\n# Basic snapshot (shows page structure)\nagent-browser snapshot\n\n# Interactive snapshot (-i flag) - RECOMMENDED\nagent-browser snapshot -i\n```\n\n### Snapshot Output Format\n\n```\nPage: Example Site - Home\nURL: https://example.com\n\n@e1 [header]\n  @e2 [nav]\n    @e3 [a] \"Home\"\n    @e4 [a] \"Products\"\n    @e5 [a] \"About\"\n  @e6 [button] \"Sign In\"\n\n@e7 [main]\n  @e8 [h1] \"Welcome\"\n  @e9 [form]\n    @e10 [input type=\"email\"] placeholder=\"Email\"\n    @e11 [input type=\"password\"] placeholder=\"Password\"\n    @e12 [button type=\"submit\"] \"Log In\"\n\n@e13 [footer]\n  @e14 [a] \"Privacy Policy\"\n```\n\n## Using Refs\n\nOnce you have refs, interact directly:\n\n```bash\n# Click the \"Sign In\" button\nagent-browser click @e6\n\n# Fill email input\nagent-browser fill @e10 \"user@example.com\"\n\n# Fill password\nagent-browser fill @e11 \"password123\"\n\n# Submit the form\nagent-browser click @e12\n```\n\n## Ref Lifecycle\n\n**IMPORTANT**: Refs are invalidated when the page changes!\n\n```bash\n# Get initial snapshot\nagent-browser snapshot -i\n# @e1 [button] \"Next\"\n\n# Click triggers page change\nagent-browser click @e1\n\n# MUST re-snapshot to get new refs!\nagent-browser snapshot -i\n# @e1 [h1] \"Page 2\"  ← Different element now!\n```\n\n## Best Practices\n\n### 1. Always Snapshot Before Interacting\n\n```bash\n# CORRECT\nagent-browser open https://example.com\nagent-browser snapshot -i          # Get refs first\nagent-browser click @e1            # Use ref\n\n# WRONG\nagent-browser open https://example.com\nagent-browser click @e1            # Ref doesn't exist yet!\n```\n\n### 2. Re-Snapshot After Navigation\n\n```bash\nagent-browser click @e5            # Navigates to new page\nagent-browser snapshot -i          # Get new refs\nagent-browser click @e1            # Use new refs\n```\n\n### 3. Re-Snapshot After Dynamic Changes\n\n```bash\nagent-browser click @e1            # Opens dropdown\nagent-browser snapshot -i          # See dropdown items\nagent-browser click @e7            # Select item\n```\n\n### 4. Snapshot Specific Regions\n\nFor complex pages, snapshot specific areas:\n\n```bash\n# Snapshot just the form\nagent-browser snapshot @e9\n```\n\n## Ref Notation Details\n\n```\n@e1 [tag type=\"value\"] \"text content\" placeholder=\"hint\"\n│    │   │             │               │\n│    │   │             │               └─ Additional attributes\n│    │   │             └─ Visible text\n│    │   └─ Key attributes shown\n│    └─ HTML tag name\n└─ Unique ref ID\n```\n\n### Common Patterns\n\n```\n@e1 [button] \"Submit\"                    # Button with text\n@e2 [input type=\"email\"]                 # Email input\n@e3 [input type=\"password\"]              # Password input\n@e4 [a href=\"/page\"] \"Link Text\"         # Anchor link\n@e5 [select]                             # Dropdown\n@e6 [textarea] placeholder=\"Message\"     # Text area\n@e7 [div class=\"modal\"]                  # Container (when relevant)\n@e8 [img alt=\"Logo\"]                     # Image\n@e9 [checkbox] checked                   # Checked checkbox\n@e10 [radio] selected                    # Selected radio\n```\n\n## Troubleshooting\n\n### \"Ref not found\" Error\n\n```bash\n# Ref may have changed - re-snapshot\nagent-browser snapshot -i\n```\n\n### Element Not Visible in Snapshot\n\n```bash\n# Scroll to reveal element\nagent-browser scroll --bottom\nagent-browser snapshot -i\n\n# Or wait for dynamic content\nagent-browser wait 1000\nagent-browser snapshot -i\n```\n\n### Too Many Elements\n\n```bash\n# Snapshot specific container\nagent-browser snapshot @e5\n\n# Or use get text for content-only extraction\nagent-browser get text @e5\n```\n",".agents/skills/agent-browser/references/video-recording.md":"# Recording Demos\n\nProduce polished, shareable demo videos with a visible cursor and minimal dead time.\n\nThis guide builds on the basics in `video-recording.md` with techniques specific to recording demos that look good to viewers.\n\n## Key Principles\n\n- **Minimize dead time** — chain commands with `&&` so tool-call overhead stays out of the video.\n- **Visible cursor** — headless Playwright has no cursor; inject a fake one via DOM.\n- **One chain per page** — navigation destroys the DOM cursor, so re-inject after every page change.\n- **No `sleep` calls** — tool execution provides enough natural pacing.\n\n## Step-by-step\n\n### 1. Set up the page BEFORE recording\n\nNavigate and snapshot **before** `record start`. Any time after recording starts is in the video.\n\n```bash\nagent-browser open <url>\n```\n```bash\nagent-browser snapshot -i\n```\n\nReview the snapshot to identify element refs (`@e1`, `@e2`, …) and plan the interaction sequence.\n\n### 2. Find element coordinates for mouse moves\n\nUse `getBoundingClientRect()` to get the (x, y) center of each target element. Do this **before** recording starts so the lookup doesn't appear in the video.\n\n```bash\nagent-browser eval \"(() => {\n  const el = document.querySelector('<selector>');\n  const r = el.getBoundingClientRect();\n  return JSON.stringify({ x: Math.round(r.x + r.width/2), y: Math.round(r.y + r.height/2) });\n})()\"\n```\n\n### 3. Inject a visible cursor\n\nHeadless Playwright doesn't render a cursor. Inject a DOM element that tracks mouse events:\n\n```bash\nagent-browser eval \"(() => {\n  const c = document.createElement('div');\n  c.style.cssText = 'width:20px;height:20px;border-radius:50%;background:rgba(255,50,50,0.85);position:fixed;top:290px;left:390px;z-index:999999;pointer-events:none;transition:top 0.15s ease,left 0.15s ease;box-shadow:0 0 8px rgba(255,50,50,0.5);';\n  document.body.appendChild(c);\n  document.addEventListener('mousemove', e => {\n    c.style.top=(e.clientY-10)+'px';\n    c.style.left=(e.clientX-10)+'px';\n  });\n})(); 'ok'\"\n```\n\n**Re-inject after every navigation** — page transitions destroy the DOM element.\n\n### 4. Chain all commands with `&&`\n\nEvery separate tool call adds seconds of dead time. Chain everything into as few bash calls as possible:\n\n```bash\nagent-browser record start ./demo.webm && agent-browser eval \"<inject cursor>\" && agent-browser mouse move 400 300 && agent-browser click @e1 && agent-browser mouse move 600 200 && agent-browser click @e2 && agent-browser record stop\n```\n\n### 5. Use `mouse move` before every click\n\nMove the cursor to the target first so viewers can follow the action:\n\n```bash\nagent-browser mouse move 623 52 && agent-browser click @e5\n```\n\n### 6. No `sleep` calls\n\nEvery millisecond counts. Tool execution itself provides enough natural pacing. Drop all `sleep` and `wait` calls entirely.\n\n### 7. One chain per page\n\nSince the cursor needs re-injection after navigation, structure as one `&&` chain per page. The recording persists across chains — only stop it in the final chain.\n\n```bash\n# Chain 1: first page → click navigates away\nagent-browser record start ./demo.webm && agent-browser eval \"<inject cursor>\" && agent-browser mouse move 400 300 && agent-browser click @e5\n```\n\n```bash\n# Chain 2: new page → finish\nagent-browser eval \"<inject cursor>\" && agent-browser mouse move 200 150 && agent-browser click @e3 && agent-browser scroll down 200 && agent-browser record stop\n```\n\n### 8. Serve the result\n\nCopy the `.webm` into `public/`, create a minimal viewer page, and share the sandbox URL.\n\n```bash\nmkdir -p public && cp demo.webm public/demo.webm\n```\n\nViewer page (`public/demo.html`):\n\n```html\n<!DOCTYPE html>\n<html>\n<head><title>Demo</title></head>\n<body style=\"margin:0;display:flex;justify-content:center;align-items:center;min-height:100vh;background:#111\">\n  <video controls autoplay style=\"max-width:90vw;max-height:90vh;border-radius:8px;box-shadow:0 4px 24px rgba(0,0,0,0.5)\" src=\"/demo.webm\"></video>\n</body>\n</html>\n```\n\n## Common Mistakes\n\n- **Recording before the page is ready** — navigate and snapshot first.\n- **Forgetting to re-inject the cursor after navigation** — the DOM element is gone.\n- **Separate tool calls instead of `&&` chains** — adds seconds of blank screen.\n- **Using `sleep` or `wait`** — unnecessary; tool execution provides natural pacing.\n- **Clicking without `mouse move`** — clicks without visible cursor movement look jarring.\n",".agents/skills/ai-sdk/SKILL.md":"---\nname: ai-sdk\ndescription: 'Answer questions about the AI SDK and help build AI-powered features. Use when developers: (1) Ask about AI SDK functions like generateText, streamText, ToolLoopAgent, embed, or tools, (2) Want to build AI agents, chatbots, RAG systems, or text generation features, (3) Have questions about AI providers (OpenAI, Anthropic, Google, etc.), streaming, tool calling, structured output, or embeddings, (4) Use React hooks like useChat or useCompletion. Triggers on: \"AI SDK\", \"Vercel AI SDK\", \"generateText\", \"streamText\", \"add AI to my app\", \"build an agent\", \"tool calling\", \"structured output\", \"useChat\".'\n---\n\n## Prerequisites\n\nBefore searching docs, check if `node_modules/ai/docs/` exists. If not, install **only** the `ai` package using the project's package manager (e.g., `pnpm add ai`).\n\nDo not install other packages at this stage. Provider packages (e.g., `@ai-sdk/openai`) and client packages (e.g., `@ai-sdk/react`) should be installed later when needed based on user requirements.\n\n## Critical: Do Not Trust Internal Knowledge\n\nEverything you know about the AI SDK is outdated or wrong. Your training data contains obsolete APIs, deprecated patterns, and incorrect usage.\n\n**When working with the AI SDK:**\n\n1. Ensure `ai` package is installed (see Prerequisites)\n2. Search `node_modules/ai/docs/` and `node_modules/ai/src/` for current APIs\n3. If not found locally, search ai-sdk.dev documentation (instructions below)\n4. Never rely on memory - always verify against source code or docs\n5. **`useChat` has changed significantly** - check [Common Errors](references/common-errors.md) before writing client code\n6. When deciding which model and provider to use (e.g. OpenAI, Anthropic, Gemini), use the Vercel AI Gateway provider unless the user specifies otherwise. See [AI Gateway Reference](references/ai-gateway.md) for usage details.\n7. **Always fetch current model IDs** - Never use model IDs from memory. Before writing code that uses a model, run `curl -s https://ai-gateway.vercel.sh/v1/models | jq -r '[.data[] | select(.id | startswith(\"provider/\")) | .id] | reverse | .[]'` (replacing `provider` with the relevant provider like `anthropic`, `openai`, or `google`) to get the full list with newest models first. Use the model with the highest version number (e.g., `claude-sonnet-4-5` over `claude-sonnet-4` over `claude-3-5-sonnet`).\n8. Run typecheck after changes to ensure code is correct\n9. **Be minimal** - Only specify options that differ from defaults. When unsure of defaults, check docs or source rather than guessing or over-specifying.\n\nIf you cannot find documentation to support your answer, state that explicitly.\n\n## Finding Documentation\n\n### ai@6.0.34+\n\nSearch bundled docs and source in `node_modules/ai/`:\n\n- **Docs**: `grep \"query\" node_modules/ai/docs/`\n- **Source**: `grep \"query\" node_modules/ai/src/`\n\nProvider packages include docs at `node_modules/@ai-sdk/<provider>/docs/`.\n\n### Earlier versions\n\n1. Search: `https://ai-sdk.dev/api/search-docs?q=your_query`\n2. Fetch `.md` URLs from results (e.g., `https://ai-sdk.dev/docs/agents/building-agents.md`)\n\n## When Typecheck Fails\n\n**Before searching source code**, grep [Common Errors](references/common-errors.md) for the failing property or function name. Many type errors are caused by deprecated APIs documented there.\n\nIf not found in common-errors.md:\n\n1. Search `node_modules/ai/src/` and `node_modules/ai/docs/`\n2. Search ai-sdk.dev (for earlier versions or if not found locally)\n\n## Building and Consuming Agents\n\n### Creating Agents\n\nAlways use the `ToolLoopAgent` pattern. Search `node_modules/ai/docs/` for current agent creation APIs.\n\n**File conventions**: See [type-safe-agents.md](references/type-safe-agents.md) for where to save agents and tools.\n\n**Type Safety**: When consuming agents with `useChat`, always use `InferAgentUIMessage<typeof agent>` for type-safe tool results. See [reference](references/type-safe-agents.md).\n\n### Consuming Agents (Framework-Specific)\n\nBefore implementing agent consumption:\n\n1. Check `package.json` to detect the project's framework/stack\n2. Search documentation for the framework's quickstart guide\n3. Follow the framework-specific patterns for streaming, API routes, and client integration\n\n## References\n\n- [Common Errors](references/common-errors.md) - Renamed parameters reference (parameters → inputSchema, etc.)\n- [AI Gateway](references/ai-gateway.md) - Gateway setup and usage\n- [Type-Safe Agents with useChat](references/type-safe-agents.md) - End-to-end type safety with InferAgentUIMessage\n- [DevTools](references/devtools.md) - Set up local debugging and observability (development only)\n",".agents/skills/ai-sdk/references/ai-gateway.md":"---\ntitle: Vercel AI Gateway\ndescription: Reference for using Vercel AI Gateway with the AI SDK.\n---\n\n# Vercel AI Gateway\n\nThe Vercel AI Gateway is the fastest way to get started with the AI SDK. It provides access to models from OpenAI, Anthropic, Google, and other providers through a single API.\n\n## Authentication\n\nAuthenticate with OIDC (for Vercel deployments) or an [AI Gateway API key](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai-gateway%2Fapi-keys&title=AI+Gateway+API+Keys):\n\n```env filename=\".env.local\"\nAI_GATEWAY_API_KEY=your_api_key_here\n```\n\n## Usage\n\nThe AI Gateway is the default global provider, so you can access models using a simple string:\n\n```ts\nimport { generateText } from 'ai';\n\nconst { text } = await generateText({\n  model: 'anthropic/claude-sonnet-4.5',\n  prompt: 'What is love?',\n});\n```\n\nYou can also explicitly import and use the gateway provider:\n\n```ts\n// Option 1: Import from 'ai' package (included by default)\nimport { gateway } from 'ai';\nmodel: gateway('anthropic/claude-sonnet-4.5');\n\n// Option 2: Install and import from '@ai-sdk/gateway' package\nimport { gateway } from '@ai-sdk/gateway';\nmodel: gateway('anthropic/claude-sonnet-4.5');\n```\n\n## Find Available Models\n\n**Important**: Always fetch the current model list before writing code. Never use model IDs from memory - they may be outdated.\n\nList all available models through the gateway API:\n\n```bash\ncurl https://ai-gateway.vercel.sh/v1/models\n```\n\nFilter by provider using `jq`. **Do not truncate with `head`** - always fetch the full list to find the latest models:\n\n```bash\n# Anthropic models\ncurl -s https://ai-gateway.vercel.sh/v1/models | jq -r '[.data[] | select(.id | startswith(\"anthropic/\")) | .id] | reverse | .[]'\n\n# OpenAI models\ncurl -s https://ai-gateway.vercel.sh/v1/models | jq -r '[.data[] | select(.id | startswith(\"openai/\")) | .id] | reverse | .[]'\n\n# Google models\ncurl -s https://ai-gateway.vercel.sh/v1/models | jq -r '[.data[] | select(.id | startswith(\"google/\")) | .id] | reverse | .[]'\n```\n\nWhen multiple versions of a model exist, use the one with the highest version number (e.g., prefer `claude-sonnet-4-5` over `claude-sonnet-4` over `claude-3-5-sonnet`).\n",".agents/skills/ai-sdk/references/common-errors.md":"---\ntitle: Common Errors\ndescription: Reference for common AI SDK errors and how to resolve them.\n---\n\n# Common Errors\n\n## `maxTokens` → `maxOutputTokens`\n\n```typescript\n// ❌ Incorrect\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  maxTokens: 512, // deprecated: use `maxOutputTokens` instead\n  prompt: 'Write a short story',\n});\n\n// ✅ Correct\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  maxOutputTokens: 512,\n  prompt: 'Write a short story',\n});\n```\n\n## `maxSteps` → `stopWhen: stepCountIs(n)`\n\n```typescript\n// ❌ Incorrect\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  tools: { weather },\n  maxSteps: 5, // deprecated: use `stopWhen: stepCountIs(n)` instead\n  prompt: 'What is the weather in NYC?',\n});\n\n// ✅ Correct\nimport { generateText, stepCountIs } from 'ai';\n\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  tools: { weather },\n  stopWhen: stepCountIs(5),\n  prompt: 'What is the weather in NYC?',\n});\n```\n\n## `parameters` → `inputSchema` (in tool definition)\n\n```typescript\n// ❌ Incorrect\nconst weatherTool = tool({\n  description: 'Get weather for a location',\n  parameters: z.object({\n    // deprecated: use `inputSchema` instead\n    location: z.string(),\n  }),\n  execute: async ({ location }) => ({ location, temp: 72 }),\n});\n\n// ✅ Correct\nconst weatherTool = tool({\n  description: 'Get weather for a location',\n  inputSchema: z.object({\n    location: z.string(),\n  }),\n  execute: async ({ location }) => ({ location, temp: 72 }),\n});\n```\n\n## `generateObject` → `generateText` with `output`\n\n`generateObject` is deprecated. Use `generateText` with the `output` option instead.\n\n```typescript\n// ❌ Deprecated\nimport { generateObject } from 'ai'; // deprecated: use `generateText` with `output` instead\n\nconst result = await generateObject({\n  // deprecated function\n  model: 'anthropic/claude-opus-4.5',\n  schema: z.object({\n    // deprecated: use `Output.object({ schema })` instead\n    recipe: z.object({\n      name: z.string(),\n      ingredients: z.array(z.string()),\n    }),\n  }),\n  prompt: 'Generate a recipe for chocolate cake',\n});\n\n// ✅ Correct\nimport { generateText, Output } from 'ai';\n\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  output: Output.object({\n    schema: z.object({\n      recipe: z.object({\n        name: z.string(),\n        ingredients: z.array(z.string()),\n      }),\n    }),\n  }),\n  prompt: 'Generate a recipe for chocolate cake',\n});\n\nconsole.log(result.output); // typed object\n```\n\n## Manual JSON parsing → `generateText` with `output`\n\n```typescript\n// ❌ Incorrect\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  prompt: `Extract the user info as JSON: { \"name\": string, \"age\": number }\n\n  Input: John is 25 years old`,\n});\nconst parsed = JSON.parse(result.text);\n\n// ✅ Correct\nimport { generateText, Output } from 'ai';\n\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  output: Output.object({\n    schema: z.object({\n      name: z.string(),\n      age: z.number(),\n    }),\n  }),\n  prompt: 'Extract the user info: John is 25 years old',\n});\n\nconsole.log(result.output); // { name: 'John', age: 25 }\n```\n\n## Other `output` options\n\n```typescript\n// Output.array - for generating arrays of items\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  output: Output.array({\n    element: z.object({\n      city: z.string(),\n      country: z.string(),\n    }),\n  }),\n  prompt: 'List 5 capital cities',\n});\n\n// Output.choice - for selecting from predefined options\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  output: Output.choice({\n    options: ['positive', 'negative', 'neutral'] as const,\n  }),\n  prompt: 'Classify the sentiment: I love this product!',\n});\n\n// Output.json - for untyped JSON output\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  output: Output.json(),\n  prompt: 'Return some JSON data',\n});\n```\n\n## `toDataStreamResponse` → `toUIMessageStreamResponse`\n\nWhen using `useChat` on the frontend, use `toUIMessageStreamResponse()` instead of `toDataStreamResponse()`. The UI message stream format is designed to work with the chat UI components and handles message state correctly.\n\n```typescript\n// ❌ Incorrect (when using useChat)\nconst result = streamText({\n  // config\n});\n\nreturn result.toDataStreamResponse(); // deprecated for useChat: use toUIMessageStreamResponse\n\n// ✅ Correct\nconst result = streamText({\n  // config\n});\n\nreturn result.toUIMessageStreamResponse();\n```\n\n## Removed managed input state in `useChat`\n\nThe `useChat` hook no longer manages input state internally. You must now manage input state manually.\n\n```tsx\n// ❌ Deprecated\nimport { useChat } from '@ai-sdk/react';\n\nexport default function Page() {\n  const {\n    input, // deprecated: manage input state manually with useState\n    handleInputChange, // deprecated: use custom onChange handler\n    handleSubmit, // deprecated: use sendMessage() instead\n  } = useChat({\n    api: '/api/chat', // deprecated: use `transport: new DefaultChatTransport({ api })` instead\n  });\n\n  return (\n    <form onSubmit={handleSubmit}>\n      <input value={input} onChange={handleInputChange} />\n      <button type=\"submit\">Send</button>\n    </form>\n  );\n}\n\n// ✅ Correct\nimport { useChat } from '@ai-sdk/react';\nimport { DefaultChatTransport } from 'ai';\nimport { useState } from 'react';\n\nexport default function Page() {\n  const [input, setInput] = useState('');\n  const { sendMessage } = useChat({\n    transport: new DefaultChatTransport({ api: '/api/chat' }),\n  });\n\n  const handleSubmit = e => {\n    e.preventDefault();\n    sendMessage({ text: input });\n    setInput('');\n  };\n\n  return (\n    <form onSubmit={handleSubmit}>\n      <input value={input} onChange={e => setInput(e.target.value)} />\n      <button type=\"submit\">Send</button>\n    </form>\n  );\n}\n```\n\n## `tool-invocation` → `tool-{toolName}` (typed tool parts)\n\nWhen rendering messages with `useChat`, use the typed tool part names (`tool-{toolName}`) instead of the generic `tool-invocation` type. This provides better type safety and access to tool-specific input/output types.\n\n> For end-to-end type-safety, see [Type-Safe Agents](type-safe-agents.md).\n\nTyped tool parts also use different property names:\n\n- `part.args` → `part.input`\n- `part.result` → `part.output`\n\n```tsx\n// ❌ Incorrect - using generic tool-invocation\n{\n  message.parts.map((part, i) => {\n    switch (part.type) {\n      case 'text':\n        return <div key={`${message.id}-${i}`}>{part.text}</div>;\n      case 'tool-invocation': // deprecated: use typed tool parts instead\n        return (\n          <pre key={`${message.id}-${i}`}>\n            {JSON.stringify(part.toolInvocation, null, 2)}\n          </pre>\n        );\n    }\n  });\n}\n\n// ✅ Correct - using typed tool parts (recommended)\n{\n  message.parts.map(part => {\n    switch (part.type) {\n      case 'text':\n        return part.text;\n      case 'tool-askForConfirmation':\n        // handle askForConfirmation tool\n        break;\n      case 'tool-getWeatherInformation':\n        // handle getWeatherInformation tool\n        break;\n    }\n  });\n}\n\n// ✅ Alternative - using isToolUIPart as a catch-all\nimport { isToolUIPart } from 'ai';\n\n{\n  message.parts.map(part => {\n    if (part.type === 'text') {\n      return part.text;\n    }\n    if (isToolUIPart(part)) {\n      // handle any tool part generically\n      return (\n        <div key={part.toolCallId}>\n          {part.toolName}: {part.state}\n        </div>\n      );\n    }\n  });\n}\n```\n\n## `useChat` state-dependent property access\n\nTool part properties are only available in certain states. TypeScript will error if you access them without checking state first.\n\n```tsx\n// ❌ Incorrect - input may be undefined during streaming\n// TS18048: 'part.input' is possibly 'undefined'\nif (part.type === 'tool-getWeather') {\n  const location = part.input.location;\n}\n\n// ✅ Correct - check for input-available or output-available\nif (\n  part.type === 'tool-getWeather' &&\n  (part.state === 'input-available' || part.state === 'output-available')\n) {\n  const location = part.input.location;\n}\n\n// ❌ Incorrect - output is only available after execution\n// TS18048: 'part.output' is possibly 'undefined'\nif (part.type === 'tool-getWeather') {\n  const weather = part.output;\n}\n\n// ✅ Correct - check for output-available\nif (part.type === 'tool-getWeather' && part.state === 'output-available') {\n  const location = part.input.location;\n  const weather = part.output;\n}\n```\n\n## `part.toolInvocation.args` → `part.input`\n\n```tsx\n// ❌ Incorrect\nif (part.type === 'tool-invocation') {\n  // deprecated: use `part.input` on typed tool parts instead\n  const location = part.toolInvocation.args.location;\n}\n\n// ✅ Correct\nif (\n  part.type === 'tool-getWeather' &&\n  (part.state === 'input-available' || part.state === 'output-available')\n) {\n  const location = part.input.location;\n}\n```\n\n## `part.toolInvocation.result` → `part.output`\n\n```tsx\n// ❌ Incorrect\nif (part.type === 'tool-invocation') {\n  // deprecated: use `part.output` on typed tool parts instead\n  const weather = part.toolInvocation.result;\n}\n\n// ✅ Correct\nif (part.type === 'tool-getWeather' && part.state === 'output-available') {\n  const weather = part.output;\n}\n```\n\n## `part.toolInvocation.toolCallId` → `part.toolCallId`\n\n```tsx\n// ❌ Incorrect\nif (part.type === 'tool-invocation') {\n  // deprecated: use `part.toolCallId` on typed tool parts instead\n  const id = part.toolInvocation.toolCallId;\n}\n\n// ✅ Correct\nif (part.type === 'tool-getWeather') {\n  const id = part.toolCallId;\n}\n```\n\n## Tool invocation states renamed\n\n```tsx\n// ❌ Incorrect\nswitch (part.toolInvocation.state) {\n  case 'partial-call': // deprecated: use `input-streaming` instead\n    return <div>Loading...</div>;\n  case 'call': // deprecated: use `input-available` instead\n    return <div>Executing...</div>;\n  case 'result': // deprecated: use `output-available` instead\n    return <div>Done</div>;\n}\n\n// ✅ Correct\nswitch (part.state) {\n  case 'input-streaming':\n    return <div>Loading...</div>;\n  case 'input-available':\n    return <div>Executing...</div>;\n  case 'output-available':\n    return <div>Done</div>;\n}\n```\n\n## `addToolResult` → `addToolOutput`\n\n```tsx\n// ❌ Incorrect\naddToolResult({\n  // deprecated: use `addToolOutput` instead\n  toolCallId: part.toolInvocation.toolCallId,\n  result: 'Yes, confirmed.', // deprecated: use `output` instead\n});\n\n// ✅ Correct\naddToolOutput({\n  tool: 'askForConfirmation',\n  toolCallId: part.toolCallId,\n  output: 'Yes, confirmed.',\n});\n```\n\n## `messages` → `uiMessages` in `createAgentUIStreamResponse`\n\n```typescript\n// ❌ Incorrect\nreturn createAgentUIStreamResponse({\n  agent: myAgent,\n  messages, // incorrect: use `uiMessages` instead\n});\n\n// ✅ Correct\nreturn createAgentUIStreamResponse({\n  agent: myAgent,\n  uiMessages: messages,\n});\n```\n",".agents/skills/ai-sdk/references/devtools.md":"---\ntitle: AI SDK DevTools\ndescription: Debug AI SDK calls by inspecting captured runs and steps.\n---\n\n# AI SDK DevTools\n\n## Why Use DevTools\n\nDevTools captures all AI SDK calls (`generateText`, `streamText`, `ToolLoopAgent`) to a local JSON file. This lets you inspect LLM requests, responses, tool calls, and multi-step interactions without manually logging.\n\n## Setup\n\nRequires AI SDK 6. Install `@ai-sdk/devtools` using your project's package manager.\n\nWrap your model with the middleware:\n\n```ts\nimport { wrapLanguageModel, gateway } from 'ai';\nimport { devToolsMiddleware } from '@ai-sdk/devtools';\n\nconst model = wrapLanguageModel({\n  model: gateway('anthropic/claude-sonnet-4.5'),\n  middleware: devToolsMiddleware(),\n});\n```\n\n## Viewing Captured Data\n\nAll runs and steps are saved to:\n\n```\n.devtools/generations.json\n```\n\nRead this file directly to inspect captured data:\n\n```bash\ncat .devtools/generations.json | jq\n```\n\nOr launch the web UI:\n\n```bash\nnpx @ai-sdk/devtools\n# Open http://localhost:4983\n```\n\n## Data Structure\n\n- **Run**: A complete multi-step interaction grouped by initial prompt\n- **Step**: A single LLM call within a run (includes input, output, tool calls, token usage)\n",".agents/skills/ai-sdk/references/type-safe-agents.md":"---\ntitle: Type-Safe useChat with Agents\ndescription: Build end-to-end type-safe agents by inferring UIMessage types from your agent definition.\n---\n\n# Type-Safe useChat with Agents\n\nBuild end-to-end type-safe agents by inferring `UIMessage` types from your agent definition for type-safe UI rendering with `useChat`.\n\n## Recommended Structure\n\n```\nlib/\n  agents/\n    my-agent.ts       # Agent definition + type export\n  tools/\n    weather-tool.ts   # Individual tool definitions\n    calculator-tool.ts\n```\n\n## Define Tools\n\n```ts\n// lib/tools/weather-tool.ts\nimport { tool } from 'ai';\nimport { z } from 'zod';\n\nexport const weatherTool = tool({\n  description: 'Get current weather for a location',\n  inputSchema: z.object({\n    location: z.string().describe('City name'),\n  }),\n  execute: async ({ location }) => {\n    return { temperature: 72, condition: 'sunny', location };\n  },\n});\n```\n\n## Define Agent and Export Type\n\n```ts\n// lib/agents/my-agent.ts\nimport { ToolLoopAgent, InferAgentUIMessage } from 'ai';\nimport { weatherTool } from '../tools/weather-tool';\nimport { calculatorTool } from '../tools/calculator-tool';\n\nexport const myAgent = new ToolLoopAgent({\n  model: 'anthropic/claude-sonnet-4',\n  instructions: 'You are a helpful assistant.',\n  tools: {\n    weather: weatherTool,\n    calculator: calculatorTool,\n  },\n});\n\n// Infer the UIMessage type from the agent\nexport type MyAgentUIMessage = InferAgentUIMessage<typeof myAgent>;\n```\n\n### With Custom Metadata\n\n```ts\n// lib/agents/my-agent.ts\nimport { z } from 'zod';\n\nconst metadataSchema = z.object({\n  createdAt: z.number(),\n  model: z.string().optional(),\n});\n\ntype MyMetadata = z.infer<typeof metadataSchema>;\n\nexport type MyAgentUIMessage = InferAgentUIMessage<typeof myAgent, MyMetadata>;\n```\n\n## Use with `useChat`\n\n```tsx\n// app/chat.tsx\nimport { useChat } from '@ai-sdk/react';\nimport type { MyAgentUIMessage } from '@/lib/agents/my-agent';\n\nexport function Chat() {\n  const { messages } = useChat<MyAgentUIMessage>();\n\n  return (\n    <div>\n      {messages.map(message => (\n        <Message key={message.id} message={message} />\n      ))}\n    </div>\n  );\n}\n```\n\n## Rendering Parts with Type Safety\n\nTool parts are typed as `tool-{toolName}` based on your agent's tools:\n\n```tsx\nfunction Message({ message }: { message: MyAgentUIMessage }) {\n  return (\n    <div>\n      {message.parts.map((part, i) => {\n        switch (part.type) {\n          case 'text':\n            return <p key={i}>{part.text}</p>;\n\n          case 'tool-weather':\n            // part.input and part.output are fully typed\n            if (part.state === 'output-available') {\n              return (\n                <div key={i}>\n                  Weather in {part.input.location}: {part.output.temperature}F\n                </div>\n              );\n            }\n            return <div key={i}>Loading weather...</div>;\n\n          case 'tool-calculator':\n            // TypeScript knows this is the calculator tool\n            return <div key={i}>Calculating...</div>;\n\n          default:\n            return null;\n        }\n      })}\n    </div>\n  );\n}\n```\n\nThe `part.type` discriminant narrows the type, giving you autocomplete and type checking for `input` and `output` based on each tool's schema.\n\n## Splitting Tool Rendering into Components\n\nWhen rendering many tools, you may want to split each tool into its own component. Use `UIToolInvocation<TOOL>` to derive a typed invocation from your tool and export it alongside the tool definition:\n\n```ts\n// lib/tools/weather-tool.ts\nimport { tool, UIToolInvocation } from 'ai';\nimport { z } from 'zod';\n\nexport const weatherTool = tool({\n  description: 'Get current weather for a location',\n  inputSchema: z.object({\n    location: z.string().describe('City name'),\n  }),\n  execute: async ({ location }) => {\n    return { temperature: 72, condition: 'sunny', location };\n  },\n});\n\n// Export the invocation type for use in UI components\nexport type WeatherToolInvocation = UIToolInvocation<typeof weatherTool>;\n```\n\nThen import only the type in your component:\n\n```tsx\n// components/weather-tool.tsx\nimport type { WeatherToolInvocation } from '@/lib/tools/weather-tool';\n\nexport function WeatherToolComponent({\n  invocation,\n}: {\n  invocation: WeatherToolInvocation;\n}) {\n  // invocation.input and invocation.output are fully typed\n  if (invocation.state === 'output-available') {\n    return (\n      <div>\n        Weather in {invocation.input.location}: {invocation.output.temperature}F\n      </div>\n    );\n  }\n  return <div>Loading weather for {invocation.input?.location}...</div>;\n}\n```\n\nUse the component in your message renderer:\n\n```tsx\nfunction Message({ message }: { message: MyAgentUIMessage }) {\n  return (\n    <div>\n      {message.parts.map((part, i) => {\n        switch (part.type) {\n          case 'text':\n            return <p key={i}>{part.text}</p>;\n          case 'tool-weather':\n            return <WeatherToolComponent key={i} invocation={part} />;\n          case 'tool-calculator':\n            return <CalculatorToolComponent key={i} invocation={part} />;\n          default:\n            return null;\n        }\n      })}\n    </div>\n  );\n}\n```\n\nThis approach keeps your tool rendering logic organized while maintaining full type safety, without needing to import the tool implementation into your UI components.\n",".agents/skills/baseline-ui/SKILL.md":"---\nname: baseline-ui\ndescription: Validates animation durations, enforces typography scale, checks component accessibility, and prevents layout anti-patterns in Tailwind CSS projects. Use when building UI components, reviewing CSS utilities, styling React views, or enforcing design consistency.\n---\n\n# Baseline UI\n\nEnforces an opinionated UI baseline to prevent AI-generated interface slop.\n\n## How to use\n\n- `/baseline-ui`\n  Apply these constraints to any UI work in this conversation.\n\n- `/baseline-ui <file>`\n  Review the file against all constraints below and output:\n  - violations (quote the exact line/snippet)\n  - why it matters (1 short sentence)\n  - a concrete fix (code-level suggestion)\n\n## Stack\n\n- MUST use Tailwind CSS defaults unless custom values already exist or are explicitly requested\n- MUST use `motion/react` (formerly `framer-motion`) when JavaScript animation is required\n- SHOULD use `tw-animate-css` for entrance and micro-animations in Tailwind CSS\n- MUST use `cn` utility (`clsx` + `tailwind-merge`) for class logic\n\n## Components\n\n- MUST use accessible component primitives for anything with keyboard or focus behavior (`Base UI`, `React Aria`, `Radix`)\n- MUST use the project’s existing component primitives first\n- NEVER mix primitive systems within the same interaction surface\n- SHOULD prefer [`Base UI`](https://base-ui.com/react/components) for new primitives if compatible with the stack\n- MUST add an `aria-label` to icon-only buttons\n- NEVER rebuild keyboard or focus behavior by hand unless explicitly requested\n\n## Interaction\n\n- MUST use an `AlertDialog` for destructive or irreversible actions\n- SHOULD use structural skeletons for loading states\n- NEVER use `h-screen`, use `h-dvh`\n- MUST respect `safe-area-inset` for fixed elements\n- MUST show errors next to where the action happens\n- NEVER block paste in `input` or `textarea` elements\n\n## Animation\n\n- NEVER add animation unless it is explicitly requested\n- MUST animate only compositor props (`transform`, `opacity`)\n- NEVER animate layout properties (`width`, `height`, `top`, `left`, `margin`, `padding`)\n- SHOULD avoid animating paint properties (`background`, `color`) except for small, local UI (text, icons)\n- SHOULD use `ease-out` on entrance\n- NEVER exceed `200ms` for interaction feedback\n- MUST pause looping animations when off-screen\n- SHOULD respect `prefers-reduced-motion`\n- NEVER introduce custom easing curves unless explicitly requested\n- SHOULD avoid animating large images or full-screen surfaces\n\n## Typography\n\n- MUST use `text-balance` for headings and `text-pretty` for body/paragraphs\n- MUST use `tabular-nums` for data\n- SHOULD use `truncate` or `line-clamp` for dense UI\n- NEVER modify `letter-spacing` (`tracking-*`) unless explicitly requested\n\n## Layout\n\n- MUST use a fixed `z-index` scale (no arbitrary `z-*`)\n- SHOULD use `size-*` for square elements instead of `w-*` + `h-*`\n\n## Performance\n\n- NEVER animate large `blur()` or `backdrop-filter` surfaces\n- NEVER apply `will-change` outside an active animation\n- NEVER use `useEffect` for anything that can be expressed as render logic\n\n## Design\n\n- NEVER use gradients unless explicitly requested\n- NEVER use purple or multicolor gradients\n- NEVER use glow effects as primary affordances\n- SHOULD use Tailwind CSS default shadow scale unless explicitly requested\n- MUST give empty states one clear next action\n- SHOULD limit accent color usage to one per view\n- SHOULD use existing theme or Tailwind CSS color tokens before introducing new ones\n",".agents/skills/chat-sdk/SKILL.md":"---\nname: chat-sdk\ndescription: >\n  Build multi-platform chat bots with Chat SDK (`chat` npm package). Use when developers want to\n  (1) Build a Slack, Teams, Google Chat, Discord, Telegram, GitHub, Linear, or WhatsApp bot,\n  (2) Use Chat SDK to handle mentions, direct messages, subscribed threads, reactions, slash\n  commands, cards, modals, files, or AI streaming,\n  (3) Set up webhook routes or multi-adapter bots,\n  (4) Send rich cards or streamed AI responses to chat platforms,\n  (5) Build or maintain a custom adapter or state adapter.\n  Triggers on \"chat sdk\", \"chat bot\", \"slack bot\", \"teams bot\", \"google chat bot\", \"discord bot\",\n  \"telegram bot\", \"whatsapp bot\", \"@chat-adapter\", \"@chat-adapter/state-\", \"custom adapter\",\n  \"state adapter\", \"build adapter\", and building bots that work across multiple chat platforms.\n---\n\n# Chat SDK\n\nUnified TypeScript SDK for building chat bots across Slack, Teams, Google Chat, Discord, Telegram, GitHub, Linear, and WhatsApp. Write bot logic once, deploy everywhere.\n\n## Start with published sources\n\nWhen Chat SDK is installed in a user project, inspect the published files that ship in `node_modules`:\n\n```\nnode_modules/chat/docs/                    # bundled docs\nnode_modules/chat/dist/index.d.ts          # core API types\nnode_modules/chat/dist/jsx-runtime.d.ts    # JSX runtime types\nnode_modules/chat/docs/contributing/       # adapter-authoring docs\nnode_modules/chat/docs/guides/             # framework/platform guides\n```\n\nIf one of the paths below does not exist, that package is not installed in the project yet.\n\nRead these before writing code:\n- `node_modules/chat/docs/getting-started.mdx` — install and setup\n- `node_modules/chat/docs/usage.mdx` — `Chat` config and lifecycle\n- `node_modules/chat/docs/handling-events.mdx` — event routing and handlers\n- `node_modules/chat/docs/threads-messages-channels.mdx` — thread/channel/message model\n- `node_modules/chat/docs/posting-messages.mdx` — post, edit, delete, schedule\n- `node_modules/chat/docs/streaming.mdx` — AI SDK integration and streaming semantics\n- `node_modules/chat/docs/cards.mdx` — JSX cards\n- `node_modules/chat/docs/actions.mdx` — button/select interactions\n- `node_modules/chat/docs/modals.mdx` — modal submit/close flows\n- `node_modules/chat/docs/slash-commands.mdx` — slash command routing\n- `node_modules/chat/docs/direct-messages.mdx` — DM behavior and `openDM()`\n- `node_modules/chat/docs/files.mdx` — attachments/uploads\n- `node_modules/chat/docs/state.mdx` — persistence, locking, dedupe\n- `node_modules/chat/docs/adapters.mdx` — cross-platform feature matrix\n- `node_modules/chat/docs/api/chat.mdx` — exact `Chat` API\n- `node_modules/chat/docs/api/thread.mdx` — exact `Thread` API\n- `node_modules/chat/docs/api/message.mdx` — exact `Message` API\n- `node_modules/chat/docs/api/modals.mdx` — modal element and event details\n\nFor the specific adapter or state package you are using, inspect that installed package's `dist/index.d.ts` export surface in `node_modules`.\n\n## Quick start\n\n```typescript\nimport { Chat } from \"chat\";\nimport { createSlackAdapter } from \"@chat-adapter/slack\";\nimport { createRedisState } from \"@chat-adapter/state-redis\";\n\nconst bot = new Chat({\n  userName: \"mybot\",\n  adapters: {\n    slack: createSlackAdapter(),\n  },\n  state: createRedisState(),\n  dedupeTtlMs: 600_000,\n});\n\nbot.onNewMention(async (thread) => {\n  await thread.subscribe();\n  await thread.post(\"Hello! I'm listening to this thread.\");\n});\n\nbot.onSubscribedMessage(async (thread, message) => {\n  await thread.post(`You said: ${message.text}`);\n});\n```\n\n## Core concepts\n\n- **Chat** — main entry point; coordinates adapters, routing, locks, and state\n- **Adapters** — platform-specific integrations for Slack, Teams, Google Chat, Discord, Telegram, GitHub, Linear, and WhatsApp\n- **State adapters** — persistence for subscriptions, locks, dedupe, and thread state\n- **Thread** — conversation context with `post()`, `stream()`, `subscribe()`, `setState()`, `startTyping()`\n- **Message** — normalized content with `text`, `formatted`, attachments, author info, and platform `raw`\n- **Channel** — container for threads and top-level posts\n\n## Event handlers\n\n| Handler | Trigger |\n|---------|---------|\n| `onNewMention` | Bot @-mentioned in an unsubscribed thread |\n| `onDirectMessage` | New DM in an unsubscribed DM thread |\n| `onSubscribedMessage` | Any message in a subscribed thread |\n| `onNewMessage(regex)` | Regex match in an unsubscribed thread |\n| `onReaction(emojis?)` | Emoji added or removed |\n| `onAction(actionIds?)` | Button clicks and select/radio interactions |\n| `onModalSubmit(callbackId?)` | Modal form submitted |\n| `onModalClose(callbackId?)` | Modal dismissed/cancelled |\n| `onSlashCommand(commands?)` | Slash command invocation |\n| `onAssistantThreadStarted` | Slack assistant thread opened |\n| `onAssistantContextChanged` | Slack assistant context changed |\n| `onAppHomeOpened` | Slack App Home opened |\n| `onMemberJoinedChannel` | Slack member joined channel event |\n\nRead `node_modules/chat/docs/handling-events.mdx`, `node_modules/chat/docs/actions.mdx`, `node_modules/chat/docs/modals.mdx`, and `node_modules/chat/docs/slash-commands.mdx` before wiring handlers. `onDirectMessage` behavior is documented in `node_modules/chat/docs/direct-messages.mdx`.\n\n## Streaming\n\nPass any `AsyncIterable<string>` to `thread.post()` or `thread.stream()`. For AI SDK, prefer `result.fullStream` over `result.textStream` when available so step boundaries are preserved.\n\n```typescript\nimport { ToolLoopAgent } from \"ai\";\n\nconst agent = new ToolLoopAgent({ model: \"anthropic/claude-4.5-sonnet\" });\n\nbot.onNewMention(async (thread, message) => {\n  const result = await agent.stream({ prompt: message.text });\n  await thread.post(result.fullStream);\n});\n```\n\nKey details:\n- `streamingUpdateIntervalMs` controls post+edit fallback cadence\n- `fallbackStreamingPlaceholderText` defaults to `\"...\"`; set `null` to disable\n- Structured `StreamChunk` support is Slack-only; other adapters ignore non-text chunks\n\n## Cards and modals (JSX)\n\nSet `jsxImportSource: \"chat\"` in `tsconfig.json`.\n\nCard components:\n- `Card`, `CardText`, `Section`, `Fields`, `Field`, `Button`, `CardLink`, `LinkButton`, `Actions`, `Select`, `SelectOption`, `RadioSelect`, `Table`, `Image`, `Divider`\n\nModal components:\n- `Modal`, `TextInput`, `Select`, `SelectOption`, `RadioSelect`\n\n```tsx\nawait thread.post(\n  <Card title=\"Order #1234\">\n    <CardText>Your order has been received.</CardText>\n    <Actions>\n      <Button id=\"approve\" style=\"primary\">Approve</Button>\n      <Button id=\"reject\" style=\"danger\">Reject</Button>\n    </Actions>\n  </Card>\n);\n```\n\n## Adapter inventory\n\n### Official platform adapters\n\n| Platform | Package | Factory |\n|---------|---------|---------|\n| Slack | `@chat-adapter/slack` | `createSlackAdapter` |\n| Microsoft Teams | `@chat-adapter/teams` | `createTeamsAdapter` |\n| Google Chat | `@chat-adapter/gchat` | `createGoogleChatAdapter` |\n| Discord | `@chat-adapter/discord` | `createDiscordAdapter` |\n| GitHub | `@chat-adapter/github` | `createGitHubAdapter` |\n| Linear | `@chat-adapter/linear` | `createLinearAdapter` |\n| Telegram | `@chat-adapter/telegram` | `createTelegramAdapter` |\n| WhatsApp Business Cloud | `@chat-adapter/whatsapp` | `createWhatsAppAdapter` |\n\n### Official state adapters\n\n| State backend | Package | Factory |\n|--------------|---------|---------|\n| Redis | `@chat-adapter/state-redis` | `createRedisState` |\n| ioredis | `@chat-adapter/state-ioredis` | `createIoRedisState` |\n| PostgreSQL | `@chat-adapter/state-pg` | `createPostgresState` |\n| Memory | `@chat-adapter/state-memory` | `createMemoryState` |\n\n### Community adapters\n\n- `chat-state-cloudflare-do`\n- `@beeper/chat-adapter-matrix`\n- `chat-adapter-imessage`\n- `@bitbasti/chat-adapter-webex`\n- `@resend/chat-sdk-adapter`\n- `chat-adapter-baileys`\n\n### Coming-soon platform entries\n\n- Instagram\n- Signal\n- X\n- Messenger\n\n## Building a custom adapter\n\nRead these published docs first:\n- `node_modules/chat/docs/contributing/building.mdx`\n- `node_modules/chat/docs/contributing/testing.mdx`\n- `node_modules/chat/docs/contributing/publishing.mdx`\n\nAlso inspect:\n- `node_modules/chat/dist/index.d.ts` — `Adapter` and related interfaces\n- `node_modules/@chat-adapter/shared/dist/index.d.ts` — shared errors and utilities\n- Installed official adapter `dist/index.d.ts` files — reference implementations for config and APIs\n\nA custom adapter needs request verification, webhook parsing, message/thread/channel operations, ID encoding/decoding, and a format converter. Use `BaseFormatConverter` from `chat` and shared utilities from `@chat-adapter/shared`.\n\n## Webhook setup\n\nEach registered adapter exposes `bot.webhooks.<name>`. Wire those directly to your HTTP framework routes. See `node_modules/chat/docs/guides/slack-nextjs.mdx` and `node_modules/chat/docs/guides/discord-nuxt.mdx` for framework-specific route patterns.\n",".agents/skills/code-review/SKILL.md":"---\nname: code-review\ndescription: Reviews code changes and provides actionable feedback. Use when the user asks to review a PR, diff, commit, or code changes. Triggers on \"/review\", \"review this PR\", \"review my changes\", \"code review\".\n---\n\nYou are a code reviewer. Your job is to review code changes and provide actionable feedback.\n\n## Determining What to Review\n\nBased on the input provided, determine which type of review to perform:\n\n1. **No arguments (default)**: Review all uncommitted changes\n   - Run: `git diff` for unstaged changes\n   - Run: `git diff --cached` for staged changes\n\n2. **Commit hash** (40-char SHA or short hash): Review that specific commit\n   - Run: `git show $ARGUMENTS`\n\n3. **Branch name**: Compare current branch to the specified branch\n   - Run: `git diff $ARGUMENTS...HEAD`\n\n4. **PR URL or number** (contains \"github.com\" or \"pull\" or looks like a PR number): Review the pull request\n   - Run: `gh pr view $ARGUMENTS` to get PR context\n   - Run: `gh pr diff $ARGUMENTS` to get the diff\n\nUse best judgement when processing input.\n\n---\n\n## Gathering Context\n\n**Diffs alone are not enough.** After getting the diff, read the entire file(s) being modified to understand the full context. Code that looks wrong in isolation may be correct given surrounding logic—and vice versa.\n\n- Use the diff to identify which files changed\n- Read the full file to understand existing patterns, control flow, and error handling\n- When changes touch inputs, auth, storage, networking, rendering, or secrets, trace the trust boundary instead of reviewing the code in isolation\n- Check for existing style guide or conventions files (CONVENTIONS.md, AGENTS.md, .editorconfig, etc.)\n\n---\n\n## What to Look For\n\n**Bugs** - Your primary focus.\n- Logic errors, off-by-one mistakes, incorrect conditionals\n- If-else guards: missing guards, incorrect branching, unreachable code paths\n- Edge cases: null/empty/undefined inputs, error conditions, race conditions\n- Broken error handling that swallows failures, throws unexpectedly or returns error types that are not caught.\n\n**Security / Safety** - Treat this as a first-class review concern, not an afterthought.\n- Assume changed code may be reachable by untrusted users or hostile input unless you can verify otherwise\n- Look for injection, XSS, auth/authz bypass, CSRF, SSRF, open redirects, path traversal, unsafe file access, secret/token exposure, privilege escalation, insecure defaults, and tenant/data isolation leaks\n- Check that validation and authorization happen at the real boundary, not only in the UI or caller\n- Verify sensitive operations fail closed, do not log secrets, and do not expand access beyond the intended actor/resource scope\n- Prefer flagging realistic exploit paths over generic \"security concern\" comments; explain the attacker-controlled input, boundary, and impact\n\n**Structure** - Does the code fit the codebase?\n- Does it follow existing patterns and conventions?\n- Are there established abstractions it should use but doesn't?\n- Excessive nesting that could be flattened with early returns or extraction\n\n**Performance** - Only flag if obviously problematic.\n- O(n²) on unbounded data, N+1 queries, blocking I/O on hot paths\n\n---\n\n## Before You Flag Something\n\n**Be certain.** If you're going to call something a bug, you need to be confident it actually is one.\n\n- Only review the changes - do not review pre-existing code that wasn't modified\n- Don't flag something as a bug if you're unsure - investigate first\n- Don't invent hypothetical problems - if an edge case matters, explain the realistic scenario where it breaks\n- For security findings, describe the concrete exploit path or trust-boundary failure instead of vague risk language\n- If you need more context to be sure, use the tools below to get it\n\n**Don't be a zealot about style.** When checking code against conventions:\n\n- Verify the code is *actually* in violation. Don't complain about else statements if early returns are already being used correctly.\n- Some \"violations\" are acceptable when they're the simplest option. A `let` statement is fine if the alternative is convoluted.\n- Excessive nesting is a legitimate concern regardless of other style choices.\n- Don't flag style preferences as issues unless they clearly violate established project conventions.\n\n---\n\n## Tools\n\nUse these to inform your review:\n\n- **Explore agent** - Find how existing code handles similar problems. Check patterns, conventions, and prior art before claiming something doesn't fit.\n\nIf you're uncertain about something and can't verify it with these tools, say \"I'm not sure about X\" rather than flagging it as a definite issue.\n\n---\n\n## Output\n\n1. If there is a bug, be direct and clear about why it is a bug.\n2. Clearly communicate severity of issues. Do not overstate severity.\n3. Critiques should clearly and explicitly communicate the scenarios, environments, or inputs that are necessary for the bug to arise. The comment should immediately indicate that the issue's severity depends on these factors.\n4. For security findings, explicitly state the attacker-controlled input, missing control, and concrete impact.\n5. Your tone should be matter-of-fact and not accusatory or overly positive. It should read as a helpful AI assistant suggestion without sounding too much like a human reviewer.\n6. Write so the reader can quickly understand the issue without reading too closely.\n7. AVOID flattery, do not give any comments that are not helpful to the reader. Avoid phrasing like \"Great job ...\", \"Thanks for ...\".\n",".agents/skills/deploy-open-harness/SKILL.md":"---\nname: deploy-open-harness\ndescription: Guides a user through collecting the credentials needed to deploy their own copy of Open Harness, deploying this repo on Vercel, and completing first-run setup. Use for requests about deploying, self-hosting, configuring credentials, or getting started with a fork of this app.\n---\n\nYou are helping a user deploy their own copy of Open Harness.\n\nBase your guidance on the current codebase, not on older Harness-era setup assumptions.\n\n## First rule: verify current requirements from the repo\n\nBefore giving deployment advice, read these files if you have not already:\n\n- `README.md`\n- `apps/web/.env.example`\n- `apps/web/lib/db/client.ts`\n- `apps/web/lib/jwe/encrypt.ts`\n- `apps/web/lib/crypto.ts`\n- `apps/web/app/api/auth/signin/vercel/route.ts`\n- `apps/web/app/api/auth/vercel/callback/route.ts`\n- `apps/web/app/api/github/app/install/route.ts`\n- `apps/web/app/api/github/app/callback/route.ts`\n- `apps/web/lib/github/app-auth.ts`\n- `apps/web/lib/redis.ts`\n- `apps/web/lib/sandbox/config.ts`\n\nIf the code and the docs disagree, trust the code and say so.\n\nDo not rely on `scripts/setup.sh`.\n\n## Goals\n\nHelp the user:\n\n1. Decide whether they want a minimal deploy or the full GitHub-enabled coding-agent flow.\n2. Collect only the credentials actually required for that scope.\n3. Understand where to obtain each credential.\n4. Deploy this repo on Vercel.\n5. Complete first-run verification.\n6. Leave with a short next-steps checklist.\n\n## Safety rules\n\n- Never ask the user to paste secrets into chat.\n- Tell them where each value belongs, but keep secret values in Vercel project env vars or local env files.\n- Separate blockers for a minimal deploy from blockers for the full GitHub-enabled flow.\n- Be explicit when something is optional.\n\n## Scope the deployment first\n\nStart by determining which path the user wants:\n\n### 1) Minimal deploy\nA working hosted app where the user can deploy it, sign in with Vercel, and use the product without GitHub repo access.\n\n### 2) Full deploy\nEverything in the minimal deploy, plus GitHub account linking, GitHub App installation, private repo access, pushes, and PR creation.\n\nIf the user is unsure, recommend **minimal deploy first**, then layer on GitHub.\n\n## Credential checklist\n\nUse this checklist when guiding the user.\n\n### Required for the app to run\n\n- `POSTGRES_URL`\n- `JWE_SECRET`\n\n### Required for a usable hosted deployment\n\n- `ENCRYPTION_KEY`\n- `NEXT_PUBLIC_VERCEL_APP_CLIENT_ID`\n- `VERCEL_APP_CLIENT_SECRET`\n\n### Required for GitHub-enabled repo flows\n\n- `NEXT_PUBLIC_GITHUB_CLIENT_ID`\n- `GITHUB_CLIENT_SECRET`\n- `GITHUB_APP_ID`\n- `GITHUB_APP_PRIVATE_KEY`\n- `NEXT_PUBLIC_GITHUB_APP_SLUG`\n- `GITHUB_WEBHOOK_SECRET`\n\n### Optional\n\n- `REDIS_URL` or `KV_URL`\n- `VERCEL_PROJECT_PRODUCTION_URL`\n- `NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL`\n- `VERCEL_SANDBOX_BASE_SNAPSHOT_ID`\n- `ELEVENLABS_API_KEY`\n\n## How to explain each credential\n\n### PostgreSQL\nTell the user to create a Postgres database and copy the connection string into `POSTGRES_URL`.\n\n### JWE secret\nExplain that this is required for session encryption.\n\nRecommended generation command:\n\n```bash\nopenssl rand -base64 32 | tr '+/' '-_' | tr -d '=\\n'\n```\n\n### Encryption key\nExplain that provider tokens are encrypted at rest and the value must be a 64-character hex string.\n\nRecommended generation command:\n\n```bash\nopenssl rand -hex 32\n```\n\n### Vercel OAuth app\nTell the user to create a Vercel OAuth app and set:\n\n- Callback URL: `https://YOUR_DOMAIN/api/auth/vercel/callback`\n- For local dev: `http://localhost:3000/api/auth/vercel/callback`\n\nStore the credentials as:\n\n- `NEXT_PUBLIC_VERCEL_APP_CLIENT_ID`\n- `VERCEL_APP_CLIENT_SECRET`\n\n### GitHub App\nTell the user they do not need a separate GitHub OAuth app. Open Harness uses the GitHub App's user authorization flow.\n\nTell the user to create a GitHub App and set:\n\n- Homepage URL: `https://YOUR_DOMAIN`\n- Callback URL: `https://YOUR_DOMAIN/api/github/app/callback`\n- Setup URL: `https://YOUR_DOMAIN/api/github/app/callback`\n- For local dev: homepage `http://localhost:3000`, callback/setup `http://localhost:3000/api/github/app/callback`\n\nAlso tell them to:\n\n- enable \"Request user authorization (OAuth) during installation\"\n- use the GitHub App Client ID and Client Secret for `NEXT_PUBLIC_GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET`\n- make the app public if they want org installs to work cleanly\n- generate a webhook secret\n- download/generate the private key\n\nStore the values as:\n\n- `NEXT_PUBLIC_GITHUB_CLIENT_ID`\n- `GITHUB_CLIENT_SECRET`\n- `GITHUB_APP_ID`\n- `GITHUB_APP_PRIVATE_KEY`\n- `NEXT_PUBLIC_GITHUB_APP_SLUG`\n- `GITHUB_WEBHOOK_SECRET`\n\nMention that `GITHUB_APP_PRIVATE_KEY` can be stored either as PEM contents with escaped newlines or as a base64-encoded PEM.\n\n### Redis / KV\nExplain that Redis is optional. It improves resumable streams, stop signaling, and caching, but it is not required for the first deploy.\n\n## Deployment flow\n\nGuide the user through this sequence:\n\n1. Fork the repo.\n2. Import it into Vercel at the repo root.\n3. Add the baseline env vars:\n   - `POSTGRES_URL`\n   - `JWE_SECRET`\n   - `ENCRYPTION_KEY`\n4. Deploy once to get a stable production URL.\n5. Create the Vercel OAuth app using that production URL.\n6. Add `NEXT_PUBLIC_VERCEL_APP_CLIENT_ID` and `VERCEL_APP_CLIENT_SECRET`.\n7. Redeploy.\n8. If the user wants the full GitHub flow, create the GitHub App using the production URL, add the GitHub env vars, and redeploy again.\n9. Optionally add Redis/KV and the production URL vars.\n\nIf the user already has a custom domain ready, it is fine to use that domain from the start instead of the default `vercel.app` production URL.\n\n## First-run verification\n\nFor a minimal deploy, walk the user through:\n\n1. Open the production site.\n2. Sign in with Vercel.\n3. Confirm they land in the app successfully.\n4. Create a session and confirm the basic UI loads.\n\nFor the full deploy, also verify:\n\n1. GitHub account linking works.\n2. GitHub App installation completes.\n3. Installations or repos appear in the UI.\n4. A repo-backed session can start.\n5. The sandbox starts and the agent can work in the repo.\n\nIf something fails, identify the missing credential or callback mismatch instead of giving generic advice.\n\n## Response format\n\nWhen helping a user, prefer this structure:\n\n1. **Target scope** — minimal or full.\n2. **Credential checklist** — grouped into required now vs optional later.\n3. **How to get each missing credential** — short, concrete instructions.\n4. **Deploy steps** — only the next actions the user should take.\n5. **Verification** — what to click/test after deploy.\n6. **Next upgrades** — Redis, GitHub, voice, custom domain, snapshot override, only if relevant.\n\nBe concise. Keep the user moving toward the next unblocker.",".agents/skills/emil-design-eng/SKILL.md":"---\nname: emil-design-eng\ndescription: This skill encodes Emil Kowalski's philosophy on UI polish, component design, animation decisions, and the invisible details that make software feel great.\n---\n\n# Design Engineering\n\nYou are a design engineer with the craft sensibility. You build interfaces where every detail compounds into something that feels right. You understand that in a world where everyone's software is good enough, taste is the differentiator.\n\n## Core Philosophy\n\n### Taste is trained, not innate\n\nGood taste is not personal preference. It is a trained instinct: the ability to see beyond the obvious and recognize what elevates. You develop it by surrounding yourself with great work, thinking deeply about why something feels good, and practicing relentlessly.\n\nWhen building UI, don't just make it work. Study why the best interfaces feel the way they do. Reverse engineer animations. Inspect interactions. Be curious.\n\n### Unseen details compound\n\nMost details users never consciously notice. That is the point. When a feature functions exactly as someone assumes it should, they proceed without giving it a second thought. That is the goal.\n\n> \"All those unseen details combine to produce something that's just stunning, like a thousand barely audible voices all singing in tune.\" - Paul Graham\n\nEvery decision below exists because the aggregate of invisible correctness creates interfaces people love without knowing why.\n\n### Beauty is leverage\n\nPeople select tools based on the overall experience, not just functionality. Good defaults and good animations are real differentiators. Beauty is underutilized in software. Use it as leverage to stand out.\n\n## Review Format (Required)\n\nWhen reviewing UI code, you MUST use a markdown table with Before/After columns. Do NOT use a list with \"Before:\" and \"After:\" on separate lines. Always output an actual markdown table like this:\n\n| Before                                | After                                                             | Why                                                                          |\n| ------------------------------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------- |\n| `transition: all 300ms`               | `transition: transform 200ms ease-out`                            | Specify exact properties; avoid `all`                                        |\n| `transform: scale(0)`                 | `transform: scale(0.95); opacity: 0`                              | Nothing in the real world appears from nothing                               |\n| `ease-in` on dropdown                 | `ease-out` with custom curve                                      | `ease-in` feels sluggish; `ease-out` gives instant feedback                  |\n| No `:active` state on button          | `transform: scale(0.97)` on `:active`                             | Buttons must feel responsive to press                                        |\n| `transform-origin: center` on popover | `transform-origin: var(--radix-popover-content-transform-origin)` | Popovers should scale from their trigger (not modals — modals stay centered) |\n\nWrong format (never do this):\n\n```\nBefore: transition: all 300ms\nAfter: transition: transform 200ms ease-out\n────────────────────────────\nBefore: scale(0)\nAfter: scale(0.95)\n```\n\nCorrect format: A single markdown table with | Before | After | Why | columns, one row per issue found. The \"Why\" column briefly explains the reasoning.\n\n## The Animation Decision Framework\n\nBefore writing any animation code, answer these questions in order:\n\n### 1. Should this animate at all?\n\n**Ask:** How often will users see this animation?\n\n| Frequency                                                   | Decision                     |\n| ----------------------------------------------------------- | ---------------------------- |\n| 100+ times/day (keyboard shortcuts, command palette toggle) | No animation. Ever.          |\n| Tens of times/day (hover effects, list navigation)          | Remove or drastically reduce |\n| Occasional (modals, drawers, toasts)                        | Standard animation           |\n| Rare/first-time (onboarding, feedback forms, celebrations)  | Can add delight              |\n\n**Never animate keyboard-initiated actions.** These actions are repeated hundreds of times daily. Animation makes them feel slow, delayed, and disconnected from the user's actions.\n\nRaycast has no open/close animation. That is the optimal experience for something used hundreds of times a day.\n\n### 2. What is the purpose?\n\nEvery animation must have a clear answer to \"why does this animate?\"\n\nValid purposes:\n\n- **Spatial consistency**: toast enters and exits from the same direction, making swipe-to-dismiss feel intuitive\n- **State indication**: a morphing feedback button shows the state change\n- **Explanation**: a marketing animation that shows how a feature works\n- **Feedback**: a button scales down on press, confirming the interface heard the user\n- **Preventing jarring changes**: elements appearing or disappearing without transition feel broken\n\nIf the purpose is just \"it looks cool\" and the user will see it often, don't animate.\n\n### 3. What easing should it use?\n\nIs the element entering or exiting?\nYes → ease-out (starts fast, feels responsive)\nNo →\nIs it moving/morphing on screen?\nYes → ease-in-out (natural acceleration/deceleration)\nIs it a hover/color change?\nYes → ease\nIs it constant motion (marquee, progress bar)?\nYes → linear\nDefault → ease-out\n\n**Critical: use custom easing curves.** The built-in CSS easings are too weak. They lack the punch that makes animations feel intentional.\n\n```css\n/* Strong ease-out for UI interactions */\n--ease-out: cubic-bezier(0.23, 1, 0.32, 1);\n\n/* Strong ease-in-out for on-screen movement */\n--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);\n\n/* iOS-like drawer curve (from Ionic Framework) */\n--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);\n```\n\n**Never use ease-in for UI animations.** It starts slow, which makes the interface feel sluggish and unresponsive. A dropdown with `ease-in` at 300ms _feels_ slower than `ease-out` at the same 300ms, because ease-in delays the initial movement — the exact moment the user is watching most closely.\n\n**Easing curve resources:** Don't create curves from scratch. Use [easing.dev](https://easing.dev/) or [easings.co](https://easings.co/) to find stronger custom variants of standard easings.\n\n### 4. How fast should it be?\n\n| Element                  | Duration      |\n| ------------------------ | ------------- |\n| Button press feedback    | 100-160ms     |\n| Tooltips, small popovers | 125-200ms     |\n| Dropdowns, selects       | 150-250ms     |\n| Modals, drawers          | 200-500ms     |\n| Marketing/explanatory    | Can be longer |\n\n**Rule: UI animations should stay under 300ms.** A 180ms dropdown feels more responsive than a 400ms one. A faster-spinning spinner makes the app feel like it loads faster, even when the load time is identical.\n\n### Perceived performance\n\nSpeed in animation is not just about feeling snappy — it directly affects how users perceive your app's performance:\n\n- A **fast-spinning spinner** makes loading feel faster (same load time, different perception)\n- A **180ms select** animation feels more responsive than a **400ms** one\n- **Instant tooltips** after the first one is open (skip delay + skip animation) make the whole toolbar feel faster\n\nThe perception of speed matters as much as actual speed. Easing amplifies this: `ease-out` at 200ms _feels_ faster than `ease-in` at 200ms because the user sees immediate movement.\n\n## Spring Animations\n\nSprings feel more natural than duration-based animations because they simulate real physics. They don't have fixed durations — they settle based on physical parameters.\n\n### When to use springs\n\n- Drag interactions with momentum\n- Elements that should feel \"alive\" (like Apple's Dynamic Island)\n- Gestures that can be interrupted mid-animation\n- Decorative mouse-tracking interactions\n\n### Spring-based mouse interactions\n\nTying visual changes directly to mouse position feels artificial because it lacks motion. Use `useSpring` from Motion (formerly Framer Motion) to interpolate value changes with spring-like behavior instead of updating immediately.\n\n```jsx\nimport { useSpring } from \"framer-motion\";\n\n// Without spring: feels artificial, instant\nconst rotation = mouseX * 0.1;\n\n// With spring: feels natural, has momentum\nconst springRotation = useSpring(mouseX * 0.1, {\n  stiffness: 100,\n  damping: 10,\n});\n```\n\nThis works because the animation is **decorative** — it doesn't serve a function. If this were a functional graph in a banking app, no animation would be better. Know when decoration helps and when it hinders.\n\n### Spring configuration\n\n**Apple's approach (recommended — easier to reason about):**\n\n```js\n{ type: \"spring\", duration: 0.5, bounce: 0.2 }\n```\n\n**Traditional physics (more control):**\n\n```js\n{ type: \"spring\", mass: 1, stiffness: 100, damping: 10 }\n```\n\nKeep bounce subtle (0.1-0.3) when used. Avoid bounce in most UI contexts. Use it for drag-to-dismiss and playful interactions.\n\n### Interruptibility advantage\n\nSprings maintain velocity when interrupted — CSS animations and keyframes restart from zero. This makes springs ideal for gestures users might change mid-motion. When you click an expanded item and quickly press Escape, a spring-based animation smoothly reverses from its current position.\n\n## Component Building Principles\n\n### Buttons must feel responsive\n\nAdd `transform: scale(0.97)` on `:active`. This gives instant feedback, making the UI feel like it is truly listening to the user.\n\n```css\n.button {\n  transition: transform 160ms ease-out;\n}\n\n.button:active {\n  transform: scale(0.97);\n}\n```\n\nThis applies to any pressable element. The scale should be subtle (0.95-0.98).\n\n### Never animate from scale(0)\n\nNothing in the real world disappears and reappears completely. Elements animating from `scale(0)` look like they come out of nowhere.\n\nStart from `scale(0.9)` or higher, combined with opacity. Even a barely-visible initial scale makes the entrance feel more natural, like a balloon that has a visible shape even when deflated.\n\n```css\n/* Bad */\n.entering {\n  transform: scale(0);\n}\n\n/* Good */\n.entering {\n  transform: scale(0.95);\n  opacity: 0;\n}\n```\n\n### Make popovers origin-aware\n\nPopovers should scale in from their trigger, not from center. The default `transform-origin: center` is wrong for almost every popover. **Exception: modals.** Modals should keep `transform-origin: center` because they are not anchored to a specific trigger — they appear centered in the viewport.\n\n```css\n/* Radix UI */\n.popover {\n  transform-origin: var(--radix-popover-content-transform-origin);\n}\n\n/* Base UI */\n.popover {\n  transform-origin: var(--transform-origin);\n}\n```\n\nWhether the user notices the difference individually does not matter. In the aggregate, unseen details become visible. They compound.\n\n### Tooltips: skip delay on subsequent hovers\n\nTooltips should delay before appearing to prevent accidental activation. But once one tooltip is open, hovering over adjacent tooltips should open them instantly with no animation. This feels faster without defeating the purpose of the initial delay.\n\n```css\n.tooltip {\n  transition:\n    transform 125ms ease-out,\n    opacity 125ms ease-out;\n  transform-origin: var(--transform-origin);\n}\n\n.tooltip[data-starting-style],\n.tooltip[data-ending-style] {\n  opacity: 0;\n  transform: scale(0.97);\n}\n\n/* Skip animation on subsequent tooltips */\n.tooltip[data-instant] {\n  transition-duration: 0ms;\n}\n```\n\n### Use CSS transitions over keyframes for interruptible UI\n\nCSS transitions can be interrupted and retargeted mid-animation. Keyframes restart from zero. For any interaction that can be triggered rapidly (adding toasts, toggling states), transitions produce smoother results.\n\n```css\n/* Interruptible - good for UI */\n.toast {\n  transition: transform 400ms ease;\n}\n\n/* Not interruptible - avoid for dynamic UI */\n@keyframes slideIn {\n  from {\n    transform: translateY(100%);\n  }\n  to {\n    transform: translateY(0);\n  }\n}\n```\n\n### Use blur to mask imperfect transitions\n\nWhen a crossfade between two states feels off despite trying different easings and durations, add subtle `filter: blur(2px)` during the transition.\n\n**Why blur works:** Without blur, you see two distinct objects during a crossfade — the old state and the new state overlapping. This looks unnatural. Blur bridges the visual gap by blending the two states together, tricking the eye into perceiving a single smooth transformation instead of two objects swapping.\n\nCombine blur with scale-on-press (`scale(0.97)`) for a polished button state transition:\n\n```css\n.button {\n  transition: transform 160ms ease-out;\n}\n\n.button:active {\n  transform: scale(0.97);\n}\n\n.button-content {\n  transition:\n    filter 200ms ease,\n    opacity 200ms ease;\n}\n\n.button-content.transitioning {\n  filter: blur(2px);\n  opacity: 0.7;\n}\n```\n\nKeep blur under 20px. Heavy blur is expensive, especially in Safari.\n\n### Animate enter states with @starting-style\n\nThe modern CSS way to animate element entry without JavaScript:\n\n```css\n.toast {\n  opacity: 1;\n  transform: translateY(0);\n  transition:\n    opacity 400ms ease,\n    transform 400ms ease;\n\n  @starting-style {\n    opacity: 0;\n    transform: translateY(100%);\n  }\n}\n```\n\nThis replaces the common React pattern of using `useEffect` to set `mounted: true` after initial render. Use `@starting-style` when browser support allows; fall back to the `data-mounted` attribute pattern otherwise.\n\n```jsx\n// Legacy pattern (still works everywhere)\nuseEffect(() => {\n  setMounted(true);\n}, []);\n// <div data-mounted={mounted}>\n```\n\n## CSS Transform Mastery\n\n### translateY with percentages\n\nPercentage values in `translate()` are relative to the element's own size. Use `translateY(100%)` to move an element by its own height, regardless of actual dimensions. This is how Sonner positions toasts and how Vaul hides the drawer before animating in.\n\n```css\n/* Works regardless of drawer height */\n.drawer-hidden {\n  transform: translateY(100%);\n}\n\n/* Works regardless of toast height */\n.toast-enter {\n  transform: translateY(-100%);\n}\n```\n\nPrefer percentages over hardcoded pixel values. They are less error-prone and adapt to content.\n\n### scale() scales children too\n\nUnlike `width`/`height`, `scale()` also scales an element's children. When scaling a button on press, the font size, icons, and content scale proportionally. This is a feature, not a bug.\n\n### 3D transforms for depth\n\n`rotateX()`, `rotateY()` with `transform-style: preserve-3d` create real 3D effects in CSS. Orbiting animations, coin flips, and depth effects are all possible without JavaScript.\n\n```css\n.wrapper {\n  transform-style: preserve-3d;\n}\n\n@keyframes orbit {\n  from {\n    transform: translate(-50%, -50%) rotateY(0deg) translateZ(72px)\n      rotateY(360deg);\n  }\n  to {\n    transform: translate(-50%, -50%) rotateY(360deg) translateZ(72px)\n      rotateY(0deg);\n  }\n}\n```\n\n### transform-origin\n\nEvery element has an anchor point from which transforms execute. The default is center. Set it to match where the trigger lives for origin-aware interactions.\n\n## clip-path for Animation\n\n`clip-path` is not just for shapes. It is one of the most powerful animation tools in CSS.\n\n### The inset shape\n\n`clip-path: inset(top right bottom left)` defines a rectangular clipping region. Each value \"eats\" into the element from that side.\n\n```css\n/* Fully hidden from right */\n.hidden {\n  clip-path: inset(0 100% 0 0);\n}\n\n/* Fully visible */\n.visible {\n  clip-path: inset(0 0 0 0);\n}\n\n/* Reveal from left to right */\n.overlay {\n  clip-path: inset(0 100% 0 0);\n  transition: clip-path 200ms ease-out;\n}\n.button:active .overlay {\n  clip-path: inset(0 0 0 0);\n  transition: clip-path 2s linear;\n}\n```\n\n### Tabs with perfect color transitions\n\nDuplicate the tab list. Style the copy as \"active\" (different background, different text color). Clip the copy so only the active tab is visible. Animate the clip on tab change. This creates a seamless color transition that timing individual color transitions can never achieve.\n\n### Hold-to-delete pattern\n\nUse `clip-path: inset(0 100% 0 0)` on a colored overlay. On `:active`, transition to `inset(0 0 0 0)` over 2s with linear timing. On release, snap back with 200ms ease-out. Add `scale(0.97)` on the button for press feedback.\n\n### Image reveals on scroll\n\nStart with `clip-path: inset(0 0 100% 0)` (hidden from bottom). Animate to `inset(0 0 0 0)` when the element enters the viewport. Use `IntersectionObserver` or Framer Motion's `useInView` with `{ once: true, margin: \"-100px\" }`.\n\n### Comparison sliders\n\nOverlay two images. Clip the top one with `clip-path: inset(0 50% 0 0)`. Adjust the right inset value based on drag position. No extra DOM elements needed, fully hardware-accelerated.\n\n## Gesture and Drag Interactions\n\n### Momentum-based dismissal\n\nDon't require dragging past a threshold. Calculate velocity: `Math.abs(dragDistance) / elapsedTime`. If velocity exceeds ~0.11, dismiss regardless of distance. A quick flick should be enough.\n\n```js\nconst timeTaken = new Date().getTime() - dragStartTime.current.getTime();\nconst velocity = Math.abs(swipeAmount) / timeTaken;\n\nif (Math.abs(swipeAmount) >= SWIPE_THRESHOLD || velocity > 0.11) {\n  dismiss();\n}\n```\n\n### Damping at boundaries\n\nWhen a user drags past the natural boundary (e.g., dragging a drawer up when already at top), apply damping. The more they drag, the less the element moves. Things in real life don't suddenly stop; they slow down first.\n\n### Pointer capture for drag\n\nOnce dragging starts, set the element to capture all pointer events. This ensures dragging continues even if the pointer leaves the element bounds.\n\n### Multi-touch protection\n\nIgnore additional touch points after the initial drag begins. Without this, switching fingers mid-drag causes the element to jump to the new position.\n\n```js\nfunction onPress() {\n  if (isDragging) return;\n  // Start drag...\n}\n```\n\n### Friction instead of hard stops\n\nInstead of preventing upward drag entirely, allow it with increasing friction. It feels more natural than hitting an invisible wall.\n\n## Performance Rules\n\n### Only animate transform and opacity\n\nThese properties skip layout and paint, running on the GPU. Animating `padding`, `margin`, `height`, or `width` triggers all three rendering steps.\n\n### CSS variables are inheritable\n\nChanging a CSS variable on a parent recalculates styles for all children. In a drawer with many items, updating `--swipe-amount` on the container causes expensive style recalculation. Update `transform` directly on the element instead.\n\n```js\n// Bad: triggers recalc on all children\nelement.style.setProperty(\"--swipe-amount\", `${distance}px`);\n\n// Good: only affects this element\nelement.style.transform = `translateY(${distance}px)`;\n```\n\n### Framer Motion hardware acceleration caveat\n\nFramer Motion's shorthand properties (`x`, `y`, `scale`) are NOT hardware-accelerated. They use `requestAnimationFrame` on the main thread. For hardware acceleration, use the full `transform` string:\n\n```jsx\n// NOT hardware accelerated (convenient but drops frames under load)\n<motion.div animate={{ x: 100 }} />\n\n// Hardware accelerated (stays smooth even when main thread is busy)\n<motion.div animate={{ transform: \"translateX(100px)\" }} />\n```\n\nThis matters when the browser is simultaneously loading content, running scripts, or painting. At Vercel, the dashboard tab animation used Shared Layout Animations and dropped frames during page loads. Switching to CSS animations (off main thread) fixed it.\n\n### CSS animations beat JS under load\n\nCSS animations run off the main thread. When the browser is busy loading a new page, Framer Motion animations (using `requestAnimationFrame`) drop frames. CSS animations remain smooth. Use CSS for predetermined animations; JS for dynamic, interruptible ones.\n\n### Use WAAPI for programmatic CSS animations\n\nThe Web Animations API gives you JavaScript control with CSS performance. Hardware-accelerated, interruptible, and no library needed.\n\n```js\nelement.animate(\n  [{ clipPath: \"inset(0 0 100% 0)\" }, { clipPath: \"inset(0 0 0 0)\" }],\n  {\n    duration: 1000,\n    fill: \"forwards\",\n    easing: \"cubic-bezier(0.77, 0, 0.175, 1)\",\n  },\n);\n```\n\n## Accessibility\n\n### prefers-reduced-motion\n\nAnimations can cause motion sickness. Reduced motion means fewer and gentler animations, not zero. Keep opacity and color transitions that aid comprehension. Remove movement and position animations.\n\n```css\n@media (prefers-reduced-motion: reduce) {\n  .element {\n    animation: fade 0.2s ease;\n    /* No transform-based motion */\n  }\n}\n```\n\n```jsx\nconst shouldReduceMotion = useReducedMotion();\nconst closedX = shouldReduceMotion ? 0 : \"-100%\";\n```\n\n### Touch device hover states\n\n```css\n@media (hover: hover) and (pointer: fine) {\n  .element:hover {\n    transform: scale(1.05);\n  }\n}\n```\n\nTouch devices trigger hover on tap, causing false positives. Gate hover animations behind this media query.\n\n## The Sonner Principles (Building Loved Components)\n\nThese principles come from building Sonner (13M+ weekly npm downloads) and apply to any component:\n\n1. **Developer experience is key.** No hooks, no context, no complex setup. Insert `<Toaster />` once, call `toast()` from anywhere. The less friction to adopt, the more people will use it.\n\n2. **Good defaults matter more than options.** Ship beautiful out of the box. Most users never customize. The default easing, timing, and visual design should be excellent.\n\n3. **Naming creates identity.** \"Sonner\" (French for \"to ring\") feels more elegant than \"react-toast\". Sacrifice discoverability for memorability when appropriate.\n\n4. **Handle edge cases invisibly.** Pause toast timers when the tab is hidden. Fill gaps between stacked toasts with pseudo-elements to maintain hover state. Capture pointer events during drag. Users never notice these, and that is exactly right.\n\n5. **Use transitions, not keyframes, for dynamic UI.** Toasts are added rapidly. Keyframes restart from zero on interruption. Transitions retarget smoothly.\n\n6. **Build a great documentation site.** Let people touch the product, play with it, and understand it before they use it. Interactive examples with ready-to-use code snippets lower the barrier to adoption.\n\n### Cohesion matters\n\nSonner's animation feels satisfying partly because the whole experience is cohesive. The easing and duration fit the vibe of the library. It is slightly slower than typical UI animations and uses `ease` rather than `ease-out` to feel more elegant. The animation style matches the toast design, the page design, the name — everything is in harmony.\n\nWhen choosing animation values, consider the personality of the component. A playful component can be bouncier. A professional dashboard should be crisp and fast. Match the motion to the mood.\n\n### The opacity + height combination\n\nWhen items enter and exit a list (like Family's drawer), the opacity change must work well with the height animation. This is often trial and error. There is no formula — you adjust until it feels right.\n\n### Review your work the next day\n\nReview animations with fresh eyes. You notice imperfections the next day that you missed during development. Play animations in slow motion or frame by frame to spot timing issues that are invisible at full speed.\n\n### Asymmetric enter/exit timing\n\nPressing should be slow when it needs to be deliberate (hold-to-delete: 2s linear), but release should always be snappy (200ms ease-out). This pattern applies broadly: slow where the user is deciding, fast where the system is responding.\n\n```css\n/* Release: fast */\n.overlay {\n  transition: clip-path 200ms ease-out;\n}\n\n/* Press: slow and deliberate */\n.button:active .overlay {\n  transition: clip-path 2s linear;\n}\n```\n\n## Stagger Animations\n\nWhen multiple elements enter together, stagger their appearance. Each element animates in with a small delay after the previous one. This creates a cascading effect that feels more natural than everything appearing at once.\n\n```css\n.item {\n  opacity: 0;\n  transform: translateY(8px);\n  animation: fadeIn 300ms ease-out forwards;\n}\n\n.item:nth-child(1) {\n  animation-delay: 0ms;\n}\n.item:nth-child(2) {\n  animation-delay: 50ms;\n}\n.item:nth-child(3) {\n  animation-delay: 100ms;\n}\n.item:nth-child(4) {\n  animation-delay: 150ms;\n}\n\n@keyframes fadeIn {\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n```\n\nKeep stagger delays short (30-80ms between items). Long delays make the interface feel slow. Stagger is decorative — never block interaction while stagger animations are playing.\n\n## Debugging Animations\n\n### Slow motion testing\n\nPlay animations at reduced speed to spot issues invisible at full speed. Temporarily increase duration to 2-5x normal, or use browser DevTools animation inspector to slow playback.\n\nThings to look for in slow motion:\n\n- Do colors transition smoothly, or do you see two distinct states overlapping?\n- Does the easing feel right, or does it start/stop abruptly?\n- Is the transform-origin correct, or does the element scale from the wrong point?\n- Are multiple animated properties (opacity, transform, color) in sync?\n\n### Frame-by-frame inspection\n\nStep through animations frame by frame in Chrome DevTools (Animations panel). This reveals timing issues between coordinated properties that you cannot see at full speed.\n\n### Test on real devices\n\nFor touch interactions (drawers, swipe gestures), test on physical devices. Connect your phone via USB, visit your local dev server by IP address, and use Safari's remote devtools. The Xcode Simulator is an alternative but real hardware is better for gesture testing.\n\n## Review Checklist\n\nWhen reviewing UI code, check for:\n\n| Issue                                  | Fix                                                                                           |\n| -------------------------------------- | --------------------------------------------------------------------------------------------- |\n| `transition: all`                      | Specify exact properties: `transition: transform 200ms ease-out`                              |\n| `scale(0)` entry animation             | Start from `scale(0.95)` with `opacity: 0`                                                    |\n| `ease-in` on UI element                | Switch to `ease-out` or custom curve                                                          |\n| `transform-origin: center` on popover  | Set to trigger location or use Radix/Base UI CSS variable (modals are exempt — keep centered) |\n| Animation on keyboard action           | Remove animation entirely                                                                     |\n| Duration > 300ms on UI element         | Reduce to 150-250ms                                                                           |\n| Hover animation without media query    | Add `@media (hover: hover) and (pointer: fine)`                                               |\n| Keyframes on rapidly-triggered element | Use CSS transitions for interruptibility                                                      |\n| Framer Motion `x`/`y` props under load | Use `transform: \"translateX()\"` for hardware acceleration                                     |\n| Same enter/exit transition speed       | Make exit faster than enter (e.g., enter 2s, exit 200ms)                                      |\n| Elements all appear at once            | Add stagger delay (30-80ms between items)                                                     |\n",".agents/skills/frontend-design/SKILL.md":"---\nname: frontend-design\ndescription: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.\n---\n\nThis skill guides creation of distinctive, production-grade frontend interfaces that avoid generic \"AI slop\" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.\n\nThe user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.\n\n## Design Thinking\n\nBefore coding, understand the context and commit to a BOLD aesthetic direction:\n- **Purpose**: What problem does this interface solve? Who uses it?\n- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.\n- **Constraints**: Technical requirements (framework, performance, accessibility).\n- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?\n\n**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.\n\nThen implement working code (HTML/CSS/JS, React, Vue, etc.) that is:\n- Production-grade and functional\n- Visually striking and memorable\n- Cohesive with a clear aesthetic point-of-view\n- Meticulously refined in every detail\n\n## Frontend Aesthetics Guidelines\n\nFocus on:\n- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.\n- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.\n- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.\n- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.\n- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.\n\nNEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.\n\nInterpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.\n\n**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.\n\nRemember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.\n",".agents/skills/plan-mode/SKILL.md":"---\nname: plan-mode\ndescription: Holistic, system-aware planning before implementing non-trivial tasks. Use when the task involves new features, architectural decisions, multi-file changes, unclear requirements, or multiple valid approaches. Triggers on \"/plan\", \"plan this\", \"design an approach\", \"let's plan first\".\n---\n\nFor non-trivial implementation tasks, plan before you code. Treat every change as a system change, not an isolated patch. Your job is not just to find a working implementation; it is to find the smallest implementation that remains coherent with the architecture, lifecycle, and future evolution of the system.\n\n## Planning Doctrine\n\n- **Think broadly, implement narrowly.** Analyze the surrounding system before deciding where the change belongs.\n- **Every change has system implications.** Consider how the request affects data flow, ownership of state, interfaces, lifecycle, failure modes, observability, and future extension.\n- **Prefer authoritative, derivable designs.** If UI state or control flow can be derived from backend events, persisted state, shared protocols, or existing abstractions, prefer that over inventing transient client-only state.\n- **Choose the smallest coherent solution.** Do not default to the smallest local patch if it distorts the architecture or adds hidden follow-on complexity.\n- **Expand scope only when it simplifies the system.** Widen the implementation only when a local fix would create duplicated state, fragile coupling, lifecycle mismatches, or recurring complexity.\n- **Do not gold-plate.** Make bounded architectural improvements that materially improve coherence for the task at hand.\n\n## Plan File\n\nWrite your plan to a `PLAN.md` file in the project root. Build this file incrementally as you progress through the steps below -- do not wait until the end to write it all at once. If a plan file already exists, read it first and decide whether the current request is a new task (overwrite) or a continuation (revise).\n\n## Step 1: Explore\n\nThoroughly explore the codebase to understand the request before designing anything.\n\n- Read the relevant files and understand existing patterns, architecture, and conventions.\n- Trace the end-to-end flow, not just the local implementation point.\n- Identify the current **source of truth**, state ownership, boundaries between subsystems, and any existing invariants.\n- Identify the full lifecycle of the behavior: trigger, processing, intermediate states, completion, side effects, failure, retry, and cleanup.\n- Search for similar features and prior art in the codebase.\n- **Launch parallel explorations** when the scope is uncertain or multiple areas of the codebase are involved. Give each exploration a specific, distinct search focus (e.g., one searches for existing implementations of similar features, another explores related components, a third investigates testing patterns). Use a single agent when the task is isolated to known files or the user provided specific file paths.\n- Do not start implementing yet.\n\n## Step 2: Clarify\n\nAsk the user questions to resolve ambiguities before committing to an approach. This may cover technical implementation, desired system behavior, UI/UX, performance, edge cases, ownership of state, failure handling, or tradeoffs. You may ask multiple rounds, reading more code in between. Do not make large assumptions about user intent.\n\n## Step 3: Design\n\nBased on exploration and user input, design a concrete implementation approach:\n\n- Provide comprehensive background context including filenames, code path traces, and the surrounding system behavior discovered in Step 1.\n- Treat the request as a system modification, not just a local diff.\n- Explicitly consider both:\n  - **The most direct implementation**\n  - **The most system-coherent implementation**\n- Choose the direct implementation only if it does **not** introduce architectural distortion, duplicated state, fragile coupling, lifecycle mismatches, or hidden follow-on complexity.\n- Prefer the smallest holistic solution: think broadly about implications, then keep the implementation as narrow as possible while preserving coherence.\n- For complex tasks, consider multiple perspectives to arrive at the best approach:\n  - **New feature**: simplicity vs performance vs maintainability\n  - **Bug fix**: root cause fix vs workaround vs prevention\n  - **Refactoring**: minimal change vs clean architecture\n- For tasks that touch multiple parts of the codebase, involve large refactors, or have many edge cases, explore different approaches in parallel before converging on a recommendation.\n\nBefore finalizing the design, explicitly answer these questions:\n\n1. What part of the system is actually changing?\n2. What is the source of truth before and after this change?\n3. What new state, transitions, or invariants does this introduce?\n4. What other components, flows, or interfaces now depend on this decision?\n5. Does this create duplicated logic or state anywhere?\n6. Is there a more system-coherent place to implement this?\n7. What adjacent simplifications become possible if this is implemented correctly?\n8. What is the smallest solution that keeps the system coherent?\n\n## Step 4: Review\n\nBefore finalizing, review your design against the original request:\n\n1. Read the critical files your design depends on to verify your assumptions.\n2. Confirm the approach aligns with the user's original intent, not just a plausible interpretation of it.\n3. Review the full lifecycle and make sure the design accounts for loading, in-progress, completion, failure, retry, resume, and cleanup where applicable.\n4. Confirm any widened scope is bounded and justified by simpler system behavior, not by speculative improvement.\n5. Ask remaining clarifying questions if anything is still ambiguous.\n\n## Step 5: Present the Plan\n\nWrite the final plan to the plan file. The plan should be:\n\n- **Concise enough to scan quickly, detailed enough to execute.** Include only your recommended approach, not all alternatives.\n- **Specific about files.** List the paths of critical files to be modified and what changes in each.\n- **Explicit about system impact.** Make clear how the change affects state ownership, data flow, interfaces, or lifecycle.\n- **Verifiable.** Include how to test the changes end-to-end.\n\nStructure:\n\n```\nSummary: 1-2 sentences on the task and chosen approach\n\nContext: Key findings from exploration -- existing patterns, relevant files, constraints\n\nSystem Impact: How the change affects source of truth, data flow, lifecycle, and dependent parts of the system\n\nApproach: High-level design decision and why\n\nChanges:\n- `path/to/file.ts` - what changes and why\n- `path/to/other.ts` - what changes and why\n\nVerification:\n- How to test end-to-end\n- Relevant test commands\n- Edge cases to check\n```\n\nPresent the plan to the user and wait for approval. If the user has feedback, revise accordingly.\n\n## Step 6: Implement and Verify\n\nOnce approved:\n\n1. Implement the plan, tracking progress against each item.\n2. After completing all items, run the verification steps from the plan to confirm all items were completed correctly.\n",".agents/skills/remove-demo-limits/SKILL.md":"---\nname: remove-demo-limits\ndescription: Removes Open Harness hosted demo restrictions from a fork. Use when a maintainer wants to remove managed-template trial caps, hosted deployment gating, or \"deploy your own\" limits. Triggers on \"remove demo limits\", \"remove trial limits\", \"remove hosted restrictions\", \"open this up for my fork\", \"remove managed template restrictions\".\n---\n\nRemove the managed-template trial restrictions with the smallest coherent change.\n\n## Goal\nMake the fork behave like a normal self-hosted deployment:\n- no trial session cap\n- no trial message cap\n- no hosted-only restriction errors\n- no hosted-demo delete restrictions\n- no dead helper code or stale tests\n\n## Inspect first\nRead these files before editing:\n- `apps/web/lib/managed-template-trial.ts`\n- `apps/web/app/api/sessions/route.ts`\n- `apps/web/app/api/chat/route.ts`\n- `apps/web/app/api/sessions/[sessionId]/chats/[chatId]/messages/[messageId]/route.ts`\n- `apps/web/app/api/sessions/route.test.ts`\n- `apps/web/app/api/chat/route.test.ts`\n- `apps/web/app/api/sessions/[sessionId]/chats/[chatId]/messages/[messageId]/route.test.ts`\n\nThen search for these identifiers and strings to catch older or newer variants:\n- `isManagedTemplateTrialUser`\n- `MANAGED_TEMPLATE_TRIAL_`\n- `deploy-your-own`\n- `open-agents.dev`\n- `vercel.com`\n- `This hosted deployment includes`\n- `does not allow message deletion`\n\n## Required changes\n1. Remove the guards that block session creation, chat messages, or message deletion for managed-template users.\n2. If the auth callback still blocks non-Vercel emails on hosted deployments, remove that restriction too.\n3. Delete `apps/web/lib/managed-template-trial.ts` if it becomes unused; otherwise simplify it so no dead exports remain.\n4. Remove or update tests that assert the hosted caps, delete restrictions, or hosted-only auth gating.\n5. Do not add feature flags, env toggles, or new dependencies. Prefer deleting the restriction code outright.\n6. Keep unrelated auth, session ownership, and sandbox behavior unchanged.\n\n## Verification\nThis repo uses Bun. After the edits:\n- run `bun run ci`\n- if a failure is pre-existing, say that clearly and separate it from your changes\n\n## Output\nReport:\n- which restrictions were removed\n- which files changed\n- verification result\n",".agents/skills/vercel-react-best-practices/AGENTS.md":"# React Best Practices\n\n**Version 1.0.0**  \nVercel Engineering  \nJanuary 2026\n\n> **Note:**  \n> This document is mainly for agents and LLMs to follow when maintaining,  \n> generating, or refactoring React and Next.js codebases. Humans  \n> may also find it useful, but guidance here is optimized for automation  \n> and consistency by AI-assisted workflows.\n\n---\n\n## Abstract\n\nComprehensive 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.\n\n---\n\n## Table of Contents\n\n1. [Eliminating Waterfalls](#1-eliminating-waterfalls) — **CRITICAL**\n   - 1.1 [Defer Await Until Needed](#11-defer-await-until-needed)\n   - 1.2 [Dependency-Based Parallelization](#12-dependency-based-parallelization)\n   - 1.3 [Prevent Waterfall Chains in API Routes](#13-prevent-waterfall-chains-in-api-routes)\n   - 1.4 [Promise.all() for Independent Operations](#14-promiseall-for-independent-operations)\n   - 1.5 [Strategic Suspense Boundaries](#15-strategic-suspense-boundaries)\n2. [Bundle Size Optimization](#2-bundle-size-optimization) — **CRITICAL**\n   - 2.1 [Avoid Barrel File Imports](#21-avoid-barrel-file-imports)\n   - 2.2 [Conditional Module Loading](#22-conditional-module-loading)\n   - 2.3 [Defer Non-Critical Third-Party Libraries](#23-defer-non-critical-third-party-libraries)\n   - 2.4 [Dynamic Imports for Heavy Components](#24-dynamic-imports-for-heavy-components)\n   - 2.5 [Preload Based on User Intent](#25-preload-based-on-user-intent)\n3. [Server-Side Performance](#3-server-side-performance) — **HIGH**\n   - 3.1 [Authenticate Server Actions Like API Routes](#31-authenticate-server-actions-like-api-routes)\n   - 3.2 [Avoid Duplicate Serialization in RSC Props](#32-avoid-duplicate-serialization-in-rsc-props)\n   - 3.3 [Cross-Request LRU Caching](#33-cross-request-lru-caching)\n   - 3.4 [Hoist Static I/O to Module Level](#34-hoist-static-io-to-module-level)\n   - 3.5 [Minimize Serialization at RSC Boundaries](#35-minimize-serialization-at-rsc-boundaries)\n   - 3.6 [Parallel Data Fetching with Component Composition](#36-parallel-data-fetching-with-component-composition)\n   - 3.7 [Per-Request Deduplication with React.cache()](#37-per-request-deduplication-with-reactcache)\n   - 3.8 [Use after() for Non-Blocking Operations](#38-use-after-for-non-blocking-operations)\n4. [Client-Side Data Fetching](#4-client-side-data-fetching) — **MEDIUM-HIGH**\n   - 4.1 [Deduplicate Global Event Listeners](#41-deduplicate-global-event-listeners)\n   - 4.2 [Use Passive Event Listeners for Scrolling Performance](#42-use-passive-event-listeners-for-scrolling-performance)\n   - 4.3 [Use SWR for Automatic Deduplication](#43-use-swr-for-automatic-deduplication)\n   - 4.4 [Version and Minimize localStorage Data](#44-version-and-minimize-localstorage-data)\n5. [Re-render Optimization](#5-re-render-optimization) — **MEDIUM**\n   - 5.1 [Calculate Derived State During Rendering](#51-calculate-derived-state-during-rendering)\n   - 5.2 [Defer State Reads to Usage Point](#52-defer-state-reads-to-usage-point)\n   - 5.3 [Do not wrap a simple expression with a primitive result type in useMemo](#53-do-not-wrap-a-simple-expression-with-a-primitive-result-type-in-usememo)\n   - 5.4 [Extract Default Non-primitive Parameter Value from Memoized Component to Constant](#54-extract-default-non-primitive-parameter-value-from-memoized-component-to-constant)\n   - 5.5 [Extract to Memoized Components](#55-extract-to-memoized-components)\n   - 5.6 [Narrow Effect Dependencies](#56-narrow-effect-dependencies)\n   - 5.7 [Put Interaction Logic in Event Handlers](#57-put-interaction-logic-in-event-handlers)\n   - 5.8 [Subscribe to Derived State](#58-subscribe-to-derived-state)\n   - 5.9 [Use Functional setState Updates](#59-use-functional-setstate-updates)\n   - 5.10 [Use Lazy State Initialization](#510-use-lazy-state-initialization)\n   - 5.11 [Use Transitions for Non-Urgent Updates](#511-use-transitions-for-non-urgent-updates)\n   - 5.12 [Use useRef for Transient Values](#512-use-useref-for-transient-values)\n6. [Rendering Performance](#6-rendering-performance) — **MEDIUM**\n   - 6.1 [Animate SVG Wrapper Instead of SVG Element](#61-animate-svg-wrapper-instead-of-svg-element)\n   - 6.2 [CSS content-visibility for Long Lists](#62-css-content-visibility-for-long-lists)\n   - 6.3 [Hoist Static JSX Elements](#63-hoist-static-jsx-elements)\n   - 6.4 [Optimize SVG Precision](#64-optimize-svg-precision)\n   - 6.5 [Prevent Hydration Mismatch Without Flickering](#65-prevent-hydration-mismatch-without-flickering)\n   - 6.6 [Suppress Expected Hydration Mismatches](#66-suppress-expected-hydration-mismatches)\n   - 6.7 [Use Activity Component for Show/Hide](#67-use-activity-component-for-showhide)\n   - 6.8 [Use Explicit Conditional Rendering](#68-use-explicit-conditional-rendering)\n   - 6.9 [Use useTransition Over Manual Loading States](#69-use-usetransition-over-manual-loading-states)\n7. [JavaScript Performance](#7-javascript-performance) — **LOW-MEDIUM**\n   - 7.1 [Avoid Layout Thrashing](#71-avoid-layout-thrashing)\n   - 7.2 [Build Index Maps for Repeated Lookups](#72-build-index-maps-for-repeated-lookups)\n   - 7.3 [Cache Property Access in Loops](#73-cache-property-access-in-loops)\n   - 7.4 [Cache Repeated Function Calls](#74-cache-repeated-function-calls)\n   - 7.5 [Cache Storage API Calls](#75-cache-storage-api-calls)\n   - 7.6 [Combine Multiple Array Iterations](#76-combine-multiple-array-iterations)\n   - 7.7 [Early Length Check for Array Comparisons](#77-early-length-check-for-array-comparisons)\n   - 7.8 [Early Return from Functions](#78-early-return-from-functions)\n   - 7.9 [Hoist RegExp Creation](#79-hoist-regexp-creation)\n   - 7.10 [Use Loop for Min/Max Instead of Sort](#710-use-loop-for-minmax-instead-of-sort)\n   - 7.11 [Use Set/Map for O(1) Lookups](#711-use-setmap-for-o1-lookups)\n   - 7.12 [Use toSorted() Instead of sort() for Immutability](#712-use-tosorted-instead-of-sort-for-immutability)\n8. [Advanced Patterns](#8-advanced-patterns) — **LOW**\n   - 8.1 [Initialize App Once, Not Per Mount](#81-initialize-app-once-not-per-mount)\n   - 8.2 [Store Event Handlers in Refs](#82-store-event-handlers-in-refs)\n   - 8.3 [useEffectEvent for Stable Callback Refs](#83-useeffectevent-for-stable-callback-refs)\n\n---\n\n## 1. Eliminating Waterfalls\n\n**Impact: CRITICAL**\n\nWaterfalls are the #1 performance killer. Each sequential await adds full network latency. Eliminating them yields the largest gains.\n\n### 1.1 Defer Await Until Needed\n\n**Impact: HIGH (avoids blocking unused code paths)**\n\nMove `await` operations into the branches where they're actually used to avoid blocking code paths that don't need them.\n\n**Incorrect: blocks both branches**\n\n```typescript\nasync function handleRequest(userId: string, skipProcessing: boolean) {\n  const userData = await fetchUserData(userId)\n  \n  if (skipProcessing) {\n    // Returns immediately but still waited for userData\n    return { skipped: true }\n  }\n  \n  // Only this branch uses userData\n  return processUserData(userData)\n}\n```\n\n**Correct: only blocks when needed**\n\n```typescript\nasync function handleRequest(userId: string, skipProcessing: boolean) {\n  if (skipProcessing) {\n    // Returns immediately without waiting\n    return { skipped: true }\n  }\n  \n  // Fetch only when needed\n  const userData = await fetchUserData(userId)\n  return processUserData(userData)\n}\n```\n\n**Another example: early return optimization**\n\n```typescript\n// Incorrect: always fetches permissions\nasync function updateResource(resourceId: string, userId: string) {\n  const permissions = await fetchPermissions(userId)\n  const resource = await getResource(resourceId)\n  \n  if (!resource) {\n    return { error: 'Not found' }\n  }\n  \n  if (!permissions.canEdit) {\n    return { error: 'Forbidden' }\n  }\n  \n  return await updateResourceData(resource, permissions)\n}\n\n// Correct: fetches only when needed\nasync function updateResource(resourceId: string, userId: string) {\n  const resource = await getResource(resourceId)\n  \n  if (!resource) {\n    return { error: 'Not found' }\n  }\n  \n  const permissions = await fetchPermissions(userId)\n  \n  if (!permissions.canEdit) {\n    return { error: 'Forbidden' }\n  }\n  \n  return await updateResourceData(resource, permissions)\n}\n```\n\nThis optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive.\n\n### 1.2 Dependency-Based Parallelization\n\n**Impact: CRITICAL (2-10× improvement)**\n\nFor operations with partial dependencies, use `better-all` to maximize parallelism. It automatically starts each task at the earliest possible moment.\n\n**Incorrect: profile waits for config unnecessarily**\n\n```typescript\nconst [user, config] = await Promise.all([\n  fetchUser(),\n  fetchConfig()\n])\nconst profile = await fetchProfile(user.id)\n```\n\n**Correct: config and profile run in parallel**\n\n```typescript\nimport { all } from 'better-all'\n\nconst { user, config, profile } = await all({\n  async user() { return fetchUser() },\n  async config() { return fetchConfig() },\n  async profile() {\n    return fetchProfile((await this.$.user).id)\n  }\n})\n```\n\n**Alternative without extra dependencies:**\n\n```typescript\nconst userPromise = fetchUser()\nconst profilePromise = userPromise.then(user => fetchProfile(user.id))\n\nconst [user, config, profile] = await Promise.all([\n  userPromise,\n  fetchConfig(),\n  profilePromise\n])\n```\n\nWe can also create all the promises first, and do `Promise.all()` at the end.\n\nReference: [https://github.com/shuding/better-all](https://github.com/shuding/better-all)\n\n### 1.3 Prevent Waterfall Chains in API Routes\n\n**Impact: CRITICAL (2-10× improvement)**\n\nIn API routes and Server Actions, start independent operations immediately, even if you don't await them yet.\n\n**Incorrect: config waits for auth, data waits for both**\n\n```typescript\nexport async function GET(request: Request) {\n  const session = await auth()\n  const config = await fetchConfig()\n  const data = await fetchData(session.user.id)\n  return Response.json({ data, config })\n}\n```\n\n**Correct: auth and config start immediately**\n\n```typescript\nexport async function GET(request: Request) {\n  const sessionPromise = auth()\n  const configPromise = fetchConfig()\n  const session = await sessionPromise\n  const [config, data] = await Promise.all([\n    configPromise,\n    fetchData(session.user.id)\n  ])\n  return Response.json({ data, config })\n}\n```\n\nFor operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization).\n\n### 1.4 Promise.all() for Independent Operations\n\n**Impact: CRITICAL (2-10× improvement)**\n\nWhen async operations have no interdependencies, execute them concurrently using `Promise.all()`.\n\n**Incorrect: sequential execution, 3 round trips**\n\n```typescript\nconst user = await fetchUser()\nconst posts = await fetchPosts()\nconst comments = await fetchComments()\n```\n\n**Correct: parallel execution, 1 round trip**\n\n```typescript\nconst [user, posts, comments] = await Promise.all([\n  fetchUser(),\n  fetchPosts(),\n  fetchComments()\n])\n```\n\n### 1.5 Strategic Suspense Boundaries\n\n**Impact: HIGH (faster initial paint)**\n\nInstead of awaiting data in async components before returning JSX, use Suspense boundaries to show the wrapper UI faster while data loads.\n\n**Incorrect: wrapper blocked by data fetching**\n\n```tsx\nasync function Page() {\n  const data = await fetchData() // Blocks entire page\n  \n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <div>\n        <DataDisplay data={data} />\n      </div>\n      <div>Footer</div>\n    </div>\n  )\n}\n```\n\nThe entire layout waits for data even though only the middle section needs it.\n\n**Correct: wrapper shows immediately, data streams in**\n\n```tsx\nfunction Page() {\n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <div>\n        <Suspense fallback={<Skeleton />}>\n          <DataDisplay />\n        </Suspense>\n      </div>\n      <div>Footer</div>\n    </div>\n  )\n}\n\nasync function DataDisplay() {\n  const data = await fetchData() // Only blocks this component\n  return <div>{data.content}</div>\n}\n```\n\nSidebar, Header, and Footer render immediately. Only DataDisplay waits for data.\n\n**Alternative: share promise across components**\n\n```tsx\nfunction Page() {\n  // Start fetch immediately, but don't await\n  const dataPromise = fetchData()\n  \n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <Suspense fallback={<Skeleton />}>\n        <DataDisplay dataPromise={dataPromise} />\n        <DataSummary dataPromise={dataPromise} />\n      </Suspense>\n      <div>Footer</div>\n    </div>\n  )\n}\n\nfunction DataDisplay({ dataPromise }: { dataPromise: Promise<Data> }) {\n  const data = use(dataPromise) // Unwraps the promise\n  return <div>{data.content}</div>\n}\n\nfunction DataSummary({ dataPromise }: { dataPromise: Promise<Data> }) {\n  const data = use(dataPromise) // Reuses the same promise\n  return <div>{data.summary}</div>\n}\n```\n\nBoth components share the same promise, so only one fetch occurs. Layout renders immediately while both components wait together.\n\n**When NOT to use this pattern:**\n\n- Critical data needed for layout decisions (affects positioning)\n\n- SEO-critical content above the fold\n\n- Small, fast queries where suspense overhead isn't worth it\n\n- When you want to avoid layout shift (loading → content jump)\n\n**Trade-off:** Faster initial paint vs potential layout shift. Choose based on your UX priorities.\n\n---\n\n## 2. Bundle Size Optimization\n\n**Impact: CRITICAL**\n\nReducing initial bundle size improves Time to Interactive and Largest Contentful Paint.\n\n### 2.1 Avoid Barrel File Imports\n\n**Impact: CRITICAL (200-800ms import cost, slow builds)**\n\nImport directly from source files instead of barrel files to avoid loading thousands of unused modules. **Barrel files** are entry points that re-export multiple modules (e.g., `index.js` that does `export * from './module'`).\n\nPopular icon and component libraries can have **up to 10,000 re-exports** in their entry file. For many React packages, **it takes 200-800ms just to import them**, affecting both development speed and production cold starts.\n\n**Why tree-shaking doesn't help:** When a library is marked as external (not bundled), the bundler can't optimize it. If you bundle it to enable tree-shaking, builds become substantially slower analyzing the entire module graph.\n\n**Incorrect: imports entire library**\n\n```tsx\nimport { Check, X, Menu } from 'lucide-react'\n// Loads 1,583 modules, takes ~2.8s extra in dev\n// Runtime cost: 200-800ms on every cold start\n\nimport { Button, TextField } from '@mui/material'\n// Loads 2,225 modules, takes ~4.2s extra in dev\n```\n\n**Correct: imports only what you need**\n\n```tsx\nimport Check from 'lucide-react/dist/esm/icons/check'\nimport X from 'lucide-react/dist/esm/icons/x'\nimport Menu from 'lucide-react/dist/esm/icons/menu'\n// Loads only 3 modules (~2KB vs ~1MB)\n\nimport Button from '@mui/material/Button'\nimport TextField from '@mui/material/TextField'\n// Loads only what you use\n```\n\n**Alternative: Next.js 13.5+**\n\n```js\n// next.config.js - use optimizePackageImports\nmodule.exports = {\n  experimental: {\n    optimizePackageImports: ['lucide-react', '@mui/material']\n  }\n}\n\n// Then you can keep the ergonomic barrel imports:\nimport { Check, X, Menu } from 'lucide-react'\n// Automatically transformed to direct imports at build time\n```\n\nDirect imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR.\n\nLibraries commonly affected: `lucide-react`, `@mui/material`, `@mui/icons-material`, `@tabler/icons-react`, `react-icons`, `@headlessui/react`, `@radix-ui/react-*`, `lodash`, `ramda`, `date-fns`, `rxjs`, `react-use`.\n\nReference: [https://vercel.com/blog/how-we-optimized-package-imports-in-next-js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)\n\n### 2.2 Conditional Module Loading\n\n**Impact: HIGH (loads large data only when needed)**\n\nLoad large data or modules only when a feature is activated.\n\n**Example: lazy-load animation frames**\n\n```tsx\nfunction AnimationPlayer({ enabled, setEnabled }: { enabled: boolean; setEnabled: React.Dispatch<React.SetStateAction<boolean>> }) {\n  const [frames, setFrames] = useState<Frame[] | null>(null)\n\n  useEffect(() => {\n    if (enabled && !frames && typeof window !== 'undefined') {\n      import('./animation-frames.js')\n        .then(mod => setFrames(mod.frames))\n        .catch(() => setEnabled(false))\n    }\n  }, [enabled, frames, setEnabled])\n\n  if (!frames) return <Skeleton />\n  return <Canvas frames={frames} />\n}\n```\n\nThe `typeof window !== 'undefined'` check prevents bundling this module for SSR, optimizing server bundle size and build speed.\n\n### 2.3 Defer Non-Critical Third-Party Libraries\n\n**Impact: MEDIUM (loads after hydration)**\n\nAnalytics, logging, and error tracking don't block user interaction. Load them after hydration.\n\n**Incorrect: blocks initial bundle**\n\n```tsx\nimport { Analytics } from '@vercel/analytics/react'\n\nexport default function RootLayout({ children }) {\n  return (\n    <html>\n      <body>\n        {children}\n        <Analytics />\n      </body>\n    </html>\n  )\n}\n```\n\n**Correct: loads after hydration**\n\n```tsx\nimport dynamic from 'next/dynamic'\n\nconst Analytics = dynamic(\n  () => import('@vercel/analytics/react').then(m => m.Analytics),\n  { ssr: false }\n)\n\nexport default function RootLayout({ children }) {\n  return (\n    <html>\n      <body>\n        {children}\n        <Analytics />\n      </body>\n    </html>\n  )\n}\n```\n\n### 2.4 Dynamic Imports for Heavy Components\n\n**Impact: CRITICAL (directly affects TTI and LCP)**\n\nUse `next/dynamic` to lazy-load large components not needed on initial render.\n\n**Incorrect: Monaco bundles with main chunk ~300KB**\n\n```tsx\nimport { MonacoEditor } from './monaco-editor'\n\nfunction CodePanel({ code }: { code: string }) {\n  return <MonacoEditor value={code} />\n}\n```\n\n**Correct: Monaco loads on demand**\n\n```tsx\nimport dynamic from 'next/dynamic'\n\nconst MonacoEditor = dynamic(\n  () => import('./monaco-editor').then(m => m.MonacoEditor),\n  { ssr: false }\n)\n\nfunction CodePanel({ code }: { code: string }) {\n  return <MonacoEditor value={code} />\n}\n```\n\n### 2.5 Preload Based on User Intent\n\n**Impact: MEDIUM (reduces perceived latency)**\n\nPreload heavy bundles before they're needed to reduce perceived latency.\n\n**Example: preload on hover/focus**\n\n```tsx\nfunction EditorButton({ onClick }: { onClick: () => void }) {\n  const preload = () => {\n    if (typeof window !== 'undefined') {\n      void import('./monaco-editor')\n    }\n  }\n\n  return (\n    <button\n      onMouseEnter={preload}\n      onFocus={preload}\n      onClick={onClick}\n    >\n      Open Editor\n    </button>\n  )\n}\n```\n\n**Example: preload when feature flag is enabled**\n\n```tsx\nfunction FlagsProvider({ children, flags }: Props) {\n  useEffect(() => {\n    if (flags.editorEnabled && typeof window !== 'undefined') {\n      void import('./monaco-editor').then(mod => mod.init())\n    }\n  }, [flags.editorEnabled])\n\n  return <FlagsContext.Provider value={flags}>\n    {children}\n  </FlagsContext.Provider>\n}\n```\n\nThe `typeof window !== 'undefined'` check prevents bundling preloaded modules for SSR, optimizing server bundle size and build speed.\n\n---\n\n## 3. Server-Side Performance\n\n**Impact: HIGH**\n\nOptimizing server-side rendering and data fetching eliminates server-side waterfalls and reduces response times.\n\n### 3.1 Authenticate Server Actions Like API Routes\n\n**Impact: CRITICAL (prevents unauthorized access to server mutations)**\n\nServer Actions (functions with `\"use server\"`) are exposed as public endpoints, just like API routes. Always verify authentication and authorization **inside** each Server Action—do not rely solely on middleware, layout guards, or page-level checks, as Server Actions can be invoked directly.\n\nNext.js documentation explicitly states: \"Treat Server Actions with the same security considerations as public-facing API endpoints, and verify if the user is allowed to perform a mutation.\"\n\n**Incorrect: no authentication check**\n\n```typescript\n'use server'\n\nexport async function deleteUser(userId: string) {\n  // Anyone can call this! No auth check\n  await db.user.delete({ where: { id: userId } })\n  return { success: true }\n}\n```\n\n**Correct: authentication inside the action**\n\n```typescript\n'use server'\n\nimport { verifySession } from '@/lib/auth'\nimport { unauthorized } from '@/lib/errors'\n\nexport async function deleteUser(userId: string) {\n  // Always check auth inside the action\n  const session = await verifySession()\n  \n  if (!session) {\n    throw unauthorized('Must be logged in')\n  }\n  \n  // Check authorization too\n  if (session.user.role !== 'admin' && session.user.id !== userId) {\n    throw unauthorized('Cannot delete other users')\n  }\n  \n  await db.user.delete({ where: { id: userId } })\n  return { success: true }\n}\n```\n\n**With input validation:**\n\n```typescript\n'use server'\n\nimport { verifySession } from '@/lib/auth'\nimport { z } from 'zod'\n\nconst updateProfileSchema = z.object({\n  userId: z.string().uuid(),\n  name: z.string().min(1).max(100),\n  email: z.string().email()\n})\n\nexport async function updateProfile(data: unknown) {\n  // Validate input first\n  const validated = updateProfileSchema.parse(data)\n  \n  // Then authenticate\n  const session = await verifySession()\n  if (!session) {\n    throw new Error('Unauthorized')\n  }\n  \n  // Then authorize\n  if (session.user.id !== validated.userId) {\n    throw new Error('Can only update own profile')\n  }\n  \n  // Finally perform the mutation\n  await db.user.update({\n    where: { id: validated.userId },\n    data: {\n      name: validated.name,\n      email: validated.email\n    }\n  })\n  \n  return { success: true }\n}\n```\n\nReference: [https://nextjs.org/docs/app/guides/authentication](https://nextjs.org/docs/app/guides/authentication)\n\n### 3.2 Avoid Duplicate Serialization in RSC Props\n\n**Impact: LOW (reduces network payload by avoiding duplicate serialization)**\n\nRSC→client serialization deduplicates by object reference, not value. Same reference = serialized once; new reference = serialized again. Do transformations (`.toSorted()`, `.filter()`, `.map()`) in client, not server.\n\n**Incorrect: duplicates array**\n\n```tsx\n// RSC: sends 6 strings (2 arrays × 3 items)\n<ClientList usernames={usernames} usernamesOrdered={usernames.toSorted()} />\n```\n\n**Correct: sends 3 strings**\n\n```tsx\n// RSC: send once\n<ClientList usernames={usernames} />\n\n// Client: transform there\n'use client'\nconst sorted = useMemo(() => [...usernames].sort(), [usernames])\n```\n\n**Nested deduplication behavior:**\n\n```tsx\n// string[] - duplicates everything\nusernames={['a','b']} sorted={usernames.toSorted()} // sends 4 strings\n\n// object[] - duplicates array structure only\nusers={[{id:1},{id:2}]} sorted={users.toSorted()} // sends 2 arrays + 2 unique objects (not 4)\n```\n\nDeduplication works recursively. Impact varies by data type:\n\n- `string[]`, `number[]`, `boolean[]`: **HIGH impact** - array + all primitives fully duplicated\n\n- `object[]`: **LOW impact** - array duplicated, but nested objects deduplicated by reference\n\n**Operations breaking deduplication: create new references**\n\n- Arrays: `.toSorted()`, `.filter()`, `.map()`, `.slice()`, `[...arr]`\n\n- Objects: `{...obj}`, `Object.assign()`, `structuredClone()`, `JSON.parse(JSON.stringify())`\n\n**More examples:**\n\n```tsx\n// ❌ Bad\n<C users={users} active={users.filter(u => u.active)} />\n<C product={product} productName={product.name} />\n\n// ✅ Good\n<C users={users} />\n<C product={product} />\n// Do filtering/destructuring in client\n```\n\n**Exception:** Pass derived data when transformation is expensive or client doesn't need original.\n\n### 3.3 Cross-Request LRU Caching\n\n**Impact: HIGH (caches across requests)**\n\n`React.cache()` only works within one request. For data shared across sequential requests (user clicks button A then button B), use an LRU cache.\n\n**Implementation:**\n\n```typescript\nimport { LRUCache } from 'lru-cache'\n\nconst cache = new LRUCache<string, any>({\n  max: 1000,\n  ttl: 5 * 60 * 1000  // 5 minutes\n})\n\nexport async function getUser(id: string) {\n  const cached = cache.get(id)\n  if (cached) return cached\n\n  const user = await db.user.findUnique({ where: { id } })\n  cache.set(id, user)\n  return user\n}\n\n// Request 1: DB query, result cached\n// Request 2: cache hit, no DB query\n```\n\nUse when sequential user actions hit multiple endpoints needing the same data within seconds.\n\n**With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute):** LRU caching is especially effective because multiple concurrent requests can share the same function instance and cache. This means the cache persists across requests without needing external storage like Redis.\n\n**In traditional serverless:** Each invocation runs in isolation, so consider Redis for cross-process caching.\n\nReference: [https://github.com/isaacs/node-lru-cache](https://github.com/isaacs/node-lru-cache)\n\n### 3.4 Hoist Static I/O to Module Level\n\n**Impact: HIGH (avoids repeated file/network I/O per request)**\n\nWhen loading static assets (fonts, logos, images, config files) in route handlers or server functions, hoist the I/O operation to module level. Module-level code runs once when the module is first imported, not on every request. This eliminates redundant file system reads or network fetches that would otherwise run on every invocation.\n\n**Incorrect: reads font file on every request**\n\n**Correct: loads once at module initialization**\n\n**Alternative: synchronous file reads with Node.js fs**\n\n**General Node.js example: loading config or templates**\n\n**When to use this pattern:**\n\n- Loading fonts for OG image generation\n\n- Loading static logos, icons, or watermarks\n\n- Reading configuration files that don't change at runtime\n\n- Loading email templates or other static templates\n\n- Any static asset that's the same across all requests\n\n**When NOT to use this pattern:**\n\n- Assets that vary per request or user\n\n- Files that may change during runtime (use caching with TTL instead)\n\n- Large files that would consume too much memory if kept loaded\n\n- Sensitive data that shouldn't persist in memory\n\n**With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute):** Module-level caching is especially effective because multiple concurrent requests share the same function instance. The static assets stay loaded in memory across requests without cold start penalties.\n\n**In traditional serverless:** Each cold start re-executes module-level code, but subsequent warm invocations reuse the loaded assets until the instance is recycled.\n\n### 3.5 Minimize Serialization at RSC Boundaries\n\n**Impact: HIGH (reduces data transfer size)**\n\nThe React Server/Client boundary serializes all object properties into strings and embeds them in the HTML response and subsequent RSC requests. This serialized data directly impacts page weight and load time, so **size matters a lot**. Only pass fields that the client actually uses.\n\n**Incorrect: serializes all 50 fields**\n\n```tsx\nasync function Page() {\n  const user = await fetchUser()  // 50 fields\n  return <Profile user={user} />\n}\n\n'use client'\nfunction Profile({ user }: { user: User }) {\n  return <div>{user.name}</div>  // uses 1 field\n}\n```\n\n**Correct: serializes only 1 field**\n\n```tsx\nasync function Page() {\n  const user = await fetchUser()\n  return <Profile name={user.name} />\n}\n\n'use client'\nfunction Profile({ name }: { name: string }) {\n  return <div>{name}</div>\n}\n```\n\n### 3.6 Parallel Data Fetching with Component Composition\n\n**Impact: CRITICAL (eliminates server-side waterfalls)**\n\nReact Server Components execute sequentially within a tree. Restructure with composition to parallelize data fetching.\n\n**Incorrect: Sidebar waits for Page's fetch to complete**\n\n```tsx\nexport default async function Page() {\n  const header = await fetchHeader()\n  return (\n    <div>\n      <div>{header}</div>\n      <Sidebar />\n    </div>\n  )\n}\n\nasync function Sidebar() {\n  const items = await fetchSidebarItems()\n  return <nav>{items.map(renderItem)}</nav>\n}\n```\n\n**Correct: both fetch simultaneously**\n\n```tsx\nasync function Header() {\n  const data = await fetchHeader()\n  return <div>{data}</div>\n}\n\nasync function Sidebar() {\n  const items = await fetchSidebarItems()\n  return <nav>{items.map(renderItem)}</nav>\n}\n\nexport default function Page() {\n  return (\n    <div>\n      <Header />\n      <Sidebar />\n    </div>\n  )\n}\n```\n\n**Alternative with children prop:**\n\n```tsx\nasync function Header() {\n  const data = await fetchHeader()\n  return <div>{data}</div>\n}\n\nasync function Sidebar() {\n  const items = await fetchSidebarItems()\n  return <nav>{items.map(renderItem)}</nav>\n}\n\nfunction Layout({ children }: { children: ReactNode }) {\n  return (\n    <div>\n      <Header />\n      {children}\n    </div>\n  )\n}\n\nexport default function Page() {\n  return (\n    <Layout>\n      <Sidebar />\n    </Layout>\n  )\n}\n```\n\n### 3.7 Per-Request Deduplication with React.cache()\n\n**Impact: MEDIUM (deduplicates within request)**\n\nUse `React.cache()` for server-side request deduplication. Authentication and database queries benefit most.\n\n**Usage:**\n\n```typescript\nimport { cache } from 'react'\n\nexport const getCurrentUser = cache(async () => {\n  const session = await auth()\n  if (!session?.user?.id) return null\n  return await db.user.findUnique({\n    where: { id: session.user.id }\n  })\n})\n```\n\nWithin a single request, multiple calls to `getCurrentUser()` execute the query only once.\n\n**Avoid inline objects as arguments:**\n\n`React.cache()` uses shallow equality (`Object.is`) to determine cache hits. Inline objects create new references each call, preventing cache hits.\n\n**Incorrect: always cache miss**\n\n```typescript\nconst getUser = cache(async (params: { uid: number }) => {\n  return await db.user.findUnique({ where: { id: params.uid } })\n})\n\n// Each call creates new object, never hits cache\ngetUser({ uid: 1 })\ngetUser({ uid: 1 })  // Cache miss, runs query again\n```\n\n**Correct: cache hit**\n\n```typescript\nconst params = { uid: 1 }\ngetUser(params)  // Query runs\ngetUser(params)  // Cache hit (same reference)\n```\n\nIf you must pass objects, pass the same reference:\n\n**Next.js-Specific Note:**\n\nIn Next.js, the `fetch` API is automatically extended with request memoization. Requests with the same URL and options are automatically deduplicated within a single request, so you don't need `React.cache()` for `fetch` calls. However, `React.cache()` is still essential for other async tasks:\n\n- Database queries (Prisma, Drizzle, etc.)\n\n- Heavy computations\n\n- Authentication checks\n\n- File system operations\n\n- Any non-fetch async work\n\nUse `React.cache()` to deduplicate these operations across your component tree.\n\nReference: [https://react.dev/reference/react/cache](https://react.dev/reference/react/cache)\n\n### 3.8 Use after() for Non-Blocking Operations\n\n**Impact: MEDIUM (faster response times)**\n\nUse Next.js's `after()` to schedule work that should execute after a response is sent. This prevents logging, analytics, and other side effects from blocking the response.\n\n**Incorrect: blocks response**\n\n```tsx\nimport { logUserAction } from '@/app/utils'\n\nexport async function POST(request: Request) {\n  // Perform mutation\n  await updateDatabase(request)\n  \n  // Logging blocks the response\n  const userAgent = request.headers.get('user-agent') || 'unknown'\n  await logUserAction({ userAgent })\n  \n  return new Response(JSON.stringify({ status: 'success' }), {\n    status: 200,\n    headers: { 'Content-Type': 'application/json' }\n  })\n}\n```\n\n**Correct: non-blocking**\n\n```tsx\nimport { after } from 'next/server'\nimport { headers, cookies } from 'next/headers'\nimport { logUserAction } from '@/app/utils'\n\nexport async function POST(request: Request) {\n  // Perform mutation\n  await updateDatabase(request)\n  \n  // Log after response is sent\n  after(async () => {\n    const userAgent = (await headers()).get('user-agent') || 'unknown'\n    const sessionCookie = (await cookies()).get('session-id')?.value || 'anonymous'\n    \n    logUserAction({ sessionCookie, userAgent })\n  })\n  \n  return new Response(JSON.stringify({ status: 'success' }), {\n    status: 200,\n    headers: { 'Content-Type': 'application/json' }\n  })\n}\n```\n\nThe response is sent immediately while logging happens in the background.\n\n**Common use cases:**\n\n- Analytics tracking\n\n- Audit logging\n\n- Sending notifications\n\n- Cache invalidation\n\n- Cleanup tasks\n\n**Important notes:**\n\n- `after()` runs even if the response fails or redirects\n\n- Works in Server Actions, Route Handlers, and Server Components\n\nReference: [https://nextjs.org/docs/app/api-reference/functions/after](https://nextjs.org/docs/app/api-reference/functions/after)\n\n---\n\n## 4. Client-Side Data Fetching\n\n**Impact: MEDIUM-HIGH**\n\nAutomatic deduplication and efficient data fetching patterns reduce redundant network requests.\n\n### 4.1 Deduplicate Global Event Listeners\n\n**Impact: LOW (single listener for N components)**\n\nUse `useSWRSubscription()` to share global event listeners across component instances.\n\n**Incorrect: N instances = N listeners**\n\n```tsx\nfunction useKeyboardShortcut(key: string, callback: () => void) {\n  useEffect(() => {\n    const handler = (e: KeyboardEvent) => {\n      if (e.metaKey && e.key === key) {\n        callback()\n      }\n    }\n    window.addEventListener('keydown', handler)\n    return () => window.removeEventListener('keydown', handler)\n  }, [key, callback])\n}\n```\n\nWhen using the `useKeyboardShortcut` hook multiple times, each instance will register a new listener.\n\n**Correct: N instances = 1 listener**\n\n```tsx\nimport useSWRSubscription from 'swr/subscription'\n\n// Module-level Map to track callbacks per key\nconst keyCallbacks = new Map<string, Set<() => void>>()\n\nfunction useKeyboardShortcut(key: string, callback: () => void) {\n  // Register this callback in the Map\n  useEffect(() => {\n    if (!keyCallbacks.has(key)) {\n      keyCallbacks.set(key, new Set())\n    }\n    keyCallbacks.get(key)!.add(callback)\n\n    return () => {\n      const set = keyCallbacks.get(key)\n      if (set) {\n        set.delete(callback)\n        if (set.size === 0) {\n          keyCallbacks.delete(key)\n        }\n      }\n    }\n  }, [key, callback])\n\n  useSWRSubscription('global-keydown', () => {\n    const handler = (e: KeyboardEvent) => {\n      if (e.metaKey && keyCallbacks.has(e.key)) {\n        keyCallbacks.get(e.key)!.forEach(cb => cb())\n      }\n    }\n    window.addEventListener('keydown', handler)\n    return () => window.removeEventListener('keydown', handler)\n  })\n}\n\nfunction Profile() {\n  // Multiple shortcuts will share the same listener\n  useKeyboardShortcut('p', () => { /* ... */ }) \n  useKeyboardShortcut('k', () => { /* ... */ })\n  // ...\n}\n```\n\n### 4.2 Use Passive Event Listeners for Scrolling Performance\n\n**Impact: MEDIUM (eliminates scroll delay caused by event listeners)**\n\nAdd `{ passive: true }` to touch and wheel event listeners to enable immediate scrolling. Browsers normally wait for listeners to finish to check if `preventDefault()` is called, causing scroll delay.\n\n**Incorrect:**\n\n```typescript\nuseEffect(() => {\n  const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)\n  const handleWheel = (e: WheelEvent) => console.log(e.deltaY)\n  \n  document.addEventListener('touchstart', handleTouch)\n  document.addEventListener('wheel', handleWheel)\n  \n  return () => {\n    document.removeEventListener('touchstart', handleTouch)\n    document.removeEventListener('wheel', handleWheel)\n  }\n}, [])\n```\n\n**Correct:**\n\n```typescript\nuseEffect(() => {\n  const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)\n  const handleWheel = (e: WheelEvent) => console.log(e.deltaY)\n  \n  document.addEventListener('touchstart', handleTouch, { passive: true })\n  document.addEventListener('wheel', handleWheel, { passive: true })\n  \n  return () => {\n    document.removeEventListener('touchstart', handleTouch)\n    document.removeEventListener('wheel', handleWheel)\n  }\n}, [])\n```\n\n**Use passive when:** tracking/analytics, logging, any listener that doesn't call `preventDefault()`.\n\n**Don't use passive when:** implementing custom swipe gestures, custom zoom controls, or any listener that needs `preventDefault()`.\n\n### 4.3 Use SWR for Automatic Deduplication\n\n**Impact: MEDIUM-HIGH (automatic deduplication)**\n\nSWR enables request deduplication, caching, and revalidation across component instances.\n\n**Incorrect: no deduplication, each instance fetches**\n\n```tsx\nfunction UserList() {\n  const [users, setUsers] = useState([])\n  useEffect(() => {\n    fetch('/api/users')\n      .then(r => r.json())\n      .then(setUsers)\n  }, [])\n}\n```\n\n**Correct: multiple instances share one request**\n\n```tsx\nimport useSWR from 'swr'\n\nfunction UserList() {\n  const { data: users } = useSWR('/api/users', fetcher)\n}\n```\n\n**For immutable data:**\n\n```tsx\nimport { useImmutableSWR } from '@/lib/swr'\n\nfunction StaticContent() {\n  const { data } = useImmutableSWR('/api/config', fetcher)\n}\n```\n\n**For mutations:**\n\n```tsx\nimport { useSWRMutation } from 'swr/mutation'\n\nfunction UpdateButton() {\n  const { trigger } = useSWRMutation('/api/user', updateUser)\n  return <button onClick={() => trigger()}>Update</button>\n}\n```\n\nReference: [https://swr.vercel.app](https://swr.vercel.app)\n\n### 4.4 Version and Minimize localStorage Data\n\n**Impact: MEDIUM (prevents schema conflicts, reduces storage size)**\n\nAdd version prefix to keys and store only needed fields. Prevents schema conflicts and accidental storage of sensitive data.\n\n**Incorrect:**\n\n```typescript\n// No version, stores everything, no error handling\nlocalStorage.setItem('userConfig', JSON.stringify(fullUserObject))\nconst data = localStorage.getItem('userConfig')\n```\n\n**Correct:**\n\n```typescript\nconst VERSION = 'v2'\n\nfunction saveConfig(config: { theme: string; language: string }) {\n  try {\n    localStorage.setItem(`userConfig:${VERSION}`, JSON.stringify(config))\n  } catch {\n    // Throws in incognito/private browsing, quota exceeded, or disabled\n  }\n}\n\nfunction loadConfig() {\n  try {\n    const data = localStorage.getItem(`userConfig:${VERSION}`)\n    return data ? JSON.parse(data) : null\n  } catch {\n    return null\n  }\n}\n\n// Migration from v1 to v2\nfunction migrate() {\n  try {\n    const v1 = localStorage.getItem('userConfig:v1')\n    if (v1) {\n      const old = JSON.parse(v1)\n      saveConfig({ theme: old.darkMode ? 'dark' : 'light', language: old.lang })\n      localStorage.removeItem('userConfig:v1')\n    }\n  } catch {}\n}\n```\n\n**Store minimal fields from server responses:**\n\n```typescript\n// User object has 20+ fields, only store what UI needs\nfunction cachePrefs(user: FullUser) {\n  try {\n    localStorage.setItem('prefs:v1', JSON.stringify({\n      theme: user.preferences.theme,\n      notifications: user.preferences.notifications\n    }))\n  } catch {}\n}\n```\n\n**Always wrap in try-catch:** `getItem()` and `setItem()` throw in incognito/private browsing (Safari, Firefox), when quota exceeded, or when disabled.\n\n**Benefits:** Schema evolution via versioning, reduced storage size, prevents storing tokens/PII/internal flags.\n\n---\n\n## 5. Re-render Optimization\n\n**Impact: MEDIUM**\n\nReducing unnecessary re-renders minimizes wasted computation and improves UI responsiveness.\n\n### 5.1 Calculate Derived State During Rendering\n\n**Impact: MEDIUM (avoids redundant renders and state drift)**\n\nIf a value can be computed from current props/state, do not store it in state or update it in an effect. Derive it during render to avoid extra renders and state drift. Do not set state in effects solely in response to prop changes; prefer derived values or keyed resets instead.\n\n**Incorrect: redundant state and effect**\n\n```tsx\nfunction Form() {\n  const [firstName, setFirstName] = useState('First')\n  const [lastName, setLastName] = useState('Last')\n  const [fullName, setFullName] = useState('')\n\n  useEffect(() => {\n    setFullName(firstName + ' ' + lastName)\n  }, [firstName, lastName])\n\n  return <p>{fullName}</p>\n}\n```\n\n**Correct: derive during render**\n\n```tsx\nfunction Form() {\n  const [firstName, setFirstName] = useState('First')\n  const [lastName, setLastName] = useState('Last')\n  const fullName = firstName + ' ' + lastName\n\n  return <p>{fullName}</p>\n}\n```\n\nReference: [https://react.dev/learn/you-might-not-need-an-effect](https://react.dev/learn/you-might-not-need-an-effect)\n\n### 5.2 Defer State Reads to Usage Point\n\n**Impact: MEDIUM (avoids unnecessary subscriptions)**\n\nDon't subscribe to dynamic state (searchParams, localStorage) if you only read it inside callbacks.\n\n**Incorrect: subscribes to all searchParams changes**\n\n```tsx\nfunction ShareButton({ chatId }: { chatId: string }) {\n  const searchParams = useSearchParams()\n\n  const handleShare = () => {\n    const ref = searchParams.get('ref')\n    shareChat(chatId, { ref })\n  }\n\n  return <button onClick={handleShare}>Share</button>\n}\n```\n\n**Correct: reads on demand, no subscription**\n\n```tsx\nfunction ShareButton({ chatId }: { chatId: string }) {\n  const handleShare = () => {\n    const params = new URLSearchParams(window.location.search)\n    const ref = params.get('ref')\n    shareChat(chatId, { ref })\n  }\n\n  return <button onClick={handleShare}>Share</button>\n}\n```\n\n### 5.3 Do not wrap a simple expression with a primitive result type in useMemo\n\n**Impact: LOW-MEDIUM (wasted computation on every render)**\n\nWhen an expression is simple (few logical or arithmetical operators) and has a primitive result type (boolean, number, string), do not wrap it in `useMemo`.\n\nCalling `useMemo` and comparing hook dependencies may consume more resources than the expression itself.\n\n**Incorrect:**\n\n```tsx\nfunction Header({ user, notifications }: Props) {\n  const isLoading = useMemo(() => {\n    return user.isLoading || notifications.isLoading\n  }, [user.isLoading, notifications.isLoading])\n\n  if (isLoading) return <Skeleton />\n  // return some markup\n}\n```\n\n**Correct:**\n\n```tsx\nfunction Header({ user, notifications }: Props) {\n  const isLoading = user.isLoading || notifications.isLoading\n\n  if (isLoading) return <Skeleton />\n  // return some markup\n}\n```\n\n### 5.4 Extract Default Non-primitive Parameter Value from Memoized Component to Constant\n\n**Impact: MEDIUM (restores memoization by using a constant for default value)**\n\nWhen memoized component has a default value for some non-primitive optional parameter, such as an array, function, or object, calling the component without that parameter results in broken memoization. This is because new value instances are created on every rerender, and they do not pass strict equality comparison in `memo()`.\n\nTo address this issue, extract the default value into a constant.\n\n**Incorrect: `onClick` has different values on every rerender**\n\n```tsx\nconst UserAvatar = memo(function UserAvatar({ onClick = () => {} }: { onClick?: () => void }) {\n  // ...\n})\n\n// Used without optional onClick\n<UserAvatar />\n```\n\n**Correct: stable default value**\n\n```tsx\nconst NOOP = () => {};\n\nconst UserAvatar = memo(function UserAvatar({ onClick = NOOP }: { onClick?: () => void }) {\n  // ...\n})\n\n// Used without optional onClick\n<UserAvatar />\n```\n\n### 5.5 Extract to Memoized Components\n\n**Impact: MEDIUM (enables early returns)**\n\nExtract expensive work into memoized components to enable early returns before computation.\n\n**Incorrect: computes avatar even when loading**\n\n```tsx\nfunction Profile({ user, loading }: Props) {\n  const avatar = useMemo(() => {\n    const id = computeAvatarId(user)\n    return <Avatar id={id} />\n  }, [user])\n\n  if (loading) return <Skeleton />\n  return <div>{avatar}</div>\n}\n```\n\n**Correct: skips computation when loading**\n\n```tsx\nconst UserAvatar = memo(function UserAvatar({ user }: { user: User }) {\n  const id = useMemo(() => computeAvatarId(user), [user])\n  return <Avatar id={id} />\n})\n\nfunction Profile({ user, loading }: Props) {\n  if (loading) return <Skeleton />\n  return (\n    <div>\n      <UserAvatar user={user} />\n    </div>\n  )\n}\n```\n\n**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, manual memoization with `memo()` and `useMemo()` is not necessary. The compiler automatically optimizes re-renders.\n\n### 5.6 Narrow Effect Dependencies\n\n**Impact: LOW (minimizes effect re-runs)**\n\nSpecify primitive dependencies instead of objects to minimize effect re-runs.\n\n**Incorrect: re-runs on any user field change**\n\n```tsx\nuseEffect(() => {\n  console.log(user.id)\n}, [user])\n```\n\n**Correct: re-runs only when id changes**\n\n```tsx\nuseEffect(() => {\n  console.log(user.id)\n}, [user.id])\n```\n\n**For derived state, compute outside effect:**\n\n```tsx\n// Incorrect: runs on width=767, 766, 765...\nuseEffect(() => {\n  if (width < 768) {\n    enableMobileMode()\n  }\n}, [width])\n\n// Correct: runs only on boolean transition\nconst isMobile = width < 768\nuseEffect(() => {\n  if (isMobile) {\n    enableMobileMode()\n  }\n}, [isMobile])\n```\n\n### 5.7 Put Interaction Logic in Event Handlers\n\n**Impact: MEDIUM (avoids effect re-runs and duplicate side effects)**\n\nIf a side effect is triggered by a specific user action (submit, click, drag), run it in that event handler. Do not model the action as state + effect; it makes effects re-run on unrelated changes and can duplicate the action.\n\n**Incorrect: event modeled as state + effect**\n\n```tsx\nfunction Form() {\n  const [submitted, setSubmitted] = useState(false)\n  const theme = useContext(ThemeContext)\n\n  useEffect(() => {\n    if (submitted) {\n      post('/api/register')\n      showToast('Registered', theme)\n    }\n  }, [submitted, theme])\n\n  return <button onClick={() => setSubmitted(true)}>Submit</button>\n}\n```\n\n**Correct: do it in the handler**\n\n```tsx\nfunction Form() {\n  const theme = useContext(ThemeContext)\n\n  function handleSubmit() {\n    post('/api/register')\n    showToast('Registered', theme)\n  }\n\n  return <button onClick={handleSubmit}>Submit</button>\n}\n```\n\nReference: [https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler](https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler)\n\n### 5.8 Subscribe to Derived State\n\n**Impact: MEDIUM (reduces re-render frequency)**\n\nSubscribe to derived boolean state instead of continuous values to reduce re-render frequency.\n\n**Incorrect: re-renders on every pixel change**\n\n```tsx\nfunction Sidebar() {\n  const width = useWindowWidth()  // updates continuously\n  const isMobile = width < 768\n  return <nav className={isMobile ? 'mobile' : 'desktop'} />\n}\n```\n\n**Correct: re-renders only when boolean changes**\n\n```tsx\nfunction Sidebar() {\n  const isMobile = useMediaQuery('(max-width: 767px)')\n  return <nav className={isMobile ? 'mobile' : 'desktop'} />\n}\n```\n\n### 5.9 Use Functional setState Updates\n\n**Impact: MEDIUM (prevents stale closures and unnecessary callback recreations)**\n\nWhen updating state based on the current state value, use the functional update form of setState instead of directly referencing the state variable. This prevents stale closures, eliminates unnecessary dependencies, and creates stable callback references.\n\n**Incorrect: requires state as dependency**\n\n```tsx\nfunction TodoList() {\n  const [items, setItems] = useState(initialItems)\n  \n  // Callback must depend on items, recreated on every items change\n  const addItems = useCallback((newItems: Item[]) => {\n    setItems([...items, ...newItems])\n  }, [items])  // ❌ items dependency causes recreations\n  \n  // Risk of stale closure if dependency is forgotten\n  const removeItem = useCallback((id: string) => {\n    setItems(items.filter(item => item.id !== id))\n  }, [])  // ❌ Missing items dependency - will use stale items!\n  \n  return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />\n}\n```\n\nThe first callback is recreated every time `items` changes, which can cause child components to re-render unnecessarily. The second callback has a stale closure bug—it will always reference the initial `items` value.\n\n**Correct: stable callbacks, no stale closures**\n\n```tsx\nfunction TodoList() {\n  const [items, setItems] = useState(initialItems)\n  \n  // Stable callback, never recreated\n  const addItems = useCallback((newItems: Item[]) => {\n    setItems(curr => [...curr, ...newItems])\n  }, [])  // ✅ No dependencies needed\n  \n  // Always uses latest state, no stale closure risk\n  const removeItem = useCallback((id: string) => {\n    setItems(curr => curr.filter(item => item.id !== id))\n  }, [])  // ✅ Safe and stable\n  \n  return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />\n}\n```\n\n**Benefits:**\n\n1. **Stable callback references** - Callbacks don't need to be recreated when state changes\n\n2. **No stale closures** - Always operates on the latest state value\n\n3. **Fewer dependencies** - Simplifies dependency arrays and reduces memory leaks\n\n4. **Prevents bugs** - Eliminates the most common source of React closure bugs\n\n**When to use functional updates:**\n\n- Any setState that depends on the current state value\n\n- Inside useCallback/useMemo when state is needed\n\n- Event handlers that reference state\n\n- Async operations that update state\n\n**When direct updates are fine:**\n\n- Setting state to a static value: `setCount(0)`\n\n- Setting state from props/arguments only: `setName(newName)`\n\n- State doesn't depend on previous value\n\n**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler can automatically optimize some cases, but functional updates are still recommended for correctness and to prevent stale closure bugs.\n\n### 5.10 Use Lazy State Initialization\n\n**Impact: \n\n<!-- [TRUNCATED AT 50KB] -->",".agents/skills/vercel-react-best-practices/README.md":"# React Best Practices\n\nA structured repository for creating and maintaining React Best Practices optimized for agents and LLMs.\n\n## Structure\n\n- `rules/` - Individual rule files (one per rule)\n  - `_sections.md` - Section metadata (titles, impacts, descriptions)\n  - `_template.md` - Template for creating new rules\n  - `area-description.md` - Individual rule files\n- `src/` - Build scripts and utilities\n- `metadata.json` - Document metadata (version, organization, abstract)\n- __`AGENTS.md`__ - Compiled output (generated)\n- __`test-cases.json`__ - Test cases for LLM evaluation (generated)\n\n## Getting Started\n\n1. Install dependencies:\n   ```bash\n   pnpm install\n   ```\n\n2. Build AGENTS.md from rules:\n   ```bash\n   pnpm build\n   ```\n\n3. Validate rule files:\n   ```bash\n   pnpm validate\n   ```\n\n4. Extract test cases:\n   ```bash\n   pnpm extract-tests\n   ```\n\n## Creating a New Rule\n\n1. Copy `rules/_template.md` to `rules/area-description.md`\n2. Choose the appropriate area prefix:\n   - `async-` for Eliminating Waterfalls (Section 1)\n   - `bundle-` for Bundle Size Optimization (Section 2)\n   - `server-` for Server-Side Performance (Section 3)\n   - `client-` for Client-Side Data Fetching (Section 4)\n   - `rerender-` for Re-render Optimization (Section 5)\n   - `rendering-` for Rendering Performance (Section 6)\n   - `js-` for JavaScript Performance (Section 7)\n   - `advanced-` for Advanced Patterns (Section 8)\n3. Fill in the frontmatter and content\n4. Ensure you have clear examples with explanations\n5. Run `pnpm build` to regenerate AGENTS.md and test-cases.json\n\n## Rule File Structure\n\nEach rule file should follow this structure:\n\n```markdown\n---\ntitle: Rule Title Here\nimpact: MEDIUM\nimpactDescription: Optional description\ntags: tag1, tag2, tag3\n---\n\n## Rule Title Here\n\nBrief explanation of the rule and why it matters.\n\n**Incorrect (description of what's wrong):**\n\n```typescript\n// Bad code example\n```\n\n**Correct (description of what's right):**\n\n```typescript\n// Good code example\n```\n\nOptional explanatory text after examples.\n\nReference: [Link](https://example.com)\n\n## File Naming Convention\n\n- Files starting with `_` are special (excluded from build)\n- Rule files: `area-description.md` (e.g., `async-parallel.md`)\n- Section is automatically inferred from filename prefix\n- Rules are sorted alphabetically by title within each section\n- IDs (e.g., 1.1, 1.2) are auto-generated during build\n\n## Impact Levels\n\n- `CRITICAL` - Highest priority, major performance gains\n- `HIGH` - Significant performance improvements\n- `MEDIUM-HIGH` - Moderate-high gains\n- `MEDIUM` - Moderate performance improvements\n- `LOW-MEDIUM` - Low-medium gains\n- `LOW` - Incremental improvements\n\n## Scripts\n\n- `pnpm build` - Compile rules into AGENTS.md\n- `pnpm validate` - Validate all rule files\n- `pnpm extract-tests` - Extract test cases for LLM evaluation\n- `pnpm dev` - Build and validate\n\n## Contributing\n\nWhen adding or modifying rules:\n\n1. Use the correct filename prefix for your section\n2. Follow the `_template.md` structure\n3. Include clear bad/good examples with explanations\n4. Add appropriate tags\n5. Run `pnpm build` to regenerate AGENTS.md and test-cases.json\n6. Rules are automatically sorted by title - no need to manage numbers!\n\n## Acknowledgments\n\nOriginally created by [@shuding](https://x.com/shuding) at [Vercel](https://vercel.com).\n",".agents/skills/vercel-react-best-practices/SKILL.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2FSKILL.md -->",".agents/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md":"---\ntitle: Store Event Handlers in Refs\nimpact: LOW\nimpactDescription: stable subscriptions\ntags: advanced, hooks, refs, event-handlers, optimization\n---\n\n## Store Event Handlers in Refs\n\nStore callbacks in refs when used in effects that shouldn't re-subscribe on callback changes.\n\n**Incorrect (re-subscribes on every render):**\n\n```tsx\nfunction useWindowEvent(event: string, handler: (e) => void) {\n  useEffect(() => {\n    window.addEventListener(event, handler)\n    return () => window.removeEventListener(event, handler)\n  }, [event, handler])\n}\n```\n\n**Correct (stable subscription):**\n\n```tsx\nfunction useWindowEvent(event: string, handler: (e) => void) {\n  const handlerRef = useRef(handler)\n  useEffect(() => {\n    handlerRef.current = handler\n  }, [handler])\n\n  useEffect(() => {\n    const listener = (e) => handlerRef.current(e)\n    window.addEventListener(event, listener)\n    return () => window.removeEventListener(event, listener)\n  }, [event])\n}\n```\n\n**Alternative: use `useEffectEvent` if you're on latest React:**\n\n```tsx\nimport { useEffectEvent } from 'react'\n\nfunction useWindowEvent(event: string, handler: (e) => void) {\n  const onEvent = useEffectEvent(handler)\n\n  useEffect(() => {\n    window.addEventListener(event, onEvent)\n    return () => window.removeEventListener(event, onEvent)\n  }, [event])\n}\n```\n\n`useEffectEvent` provides a cleaner API for the same pattern: it creates a stable function reference that always calls the latest version of the handler.\n",".agents/skills/vercel-react-best-practices/rules/advanced-init-once.md":"---\ntitle: Initialize App Once, Not Per Mount\nimpact: LOW-MEDIUM\nimpactDescription: avoids duplicate init in development\ntags: initialization, useEffect, app-startup, side-effects\n---\n\n## Initialize App Once, Not Per Mount\n\nDo not put app-wide initialization that must run once per app load inside `useEffect([])` of a component. Components can remount and effects will re-run. Use a module-level guard or top-level init in the entry module instead.\n\n**Incorrect (runs twice in dev, re-runs on remount):**\n\n```tsx\nfunction Comp() {\n  useEffect(() => {\n    loadFromStorage()\n    checkAuthToken()\n  }, [])\n\n  // ...\n}\n```\n\n**Correct (once per app load):**\n\n```tsx\nlet didInit = false\n\nfunction Comp() {\n  useEffect(() => {\n    if (didInit) return\n    didInit = true\n    loadFromStorage()\n    checkAuthToken()\n  }, [])\n\n  // ...\n}\n```\n\nReference: [Initializing the application](https://react.dev/learn/you-might-not-need-an-effect#initializing-the-application)\n",".agents/skills/vercel-react-best-practices/rules/advanced-use-latest.md":"---\ntitle: useEffectEvent for Stable Callback Refs\nimpact: LOW\nimpactDescription: prevents effect re-runs\ntags: advanced, hooks, useEffectEvent, refs, optimization\n---\n\n## useEffectEvent for Stable Callback Refs\n\nAccess latest values in callbacks without adding them to dependency arrays. Prevents effect re-runs while avoiding stale closures.\n\n**Incorrect (effect re-runs on every callback change):**\n\n```tsx\nfunction SearchInput({ onSearch }: { onSearch: (q: string) => void }) {\n  const [query, setQuery] = useState('')\n\n  useEffect(() => {\n    const timeout = setTimeout(() => onSearch(query), 300)\n    return () => clearTimeout(timeout)\n  }, [query, onSearch])\n}\n```\n\n**Correct (using React's useEffectEvent):**\n\n```tsx\nimport { useEffectEvent } from 'react';\n\nfunction SearchInput({ onSearch }: { onSearch: (q: string) => void }) {\n  const [query, setQuery] = useState('')\n  const onSearchEvent = useEffectEvent(onSearch)\n\n  useEffect(() => {\n    const timeout = setTimeout(() => onSearchEvent(query), 300)\n    return () => clearTimeout(timeout)\n  }, [query])\n}\n```\n",".agents/skills/vercel-react-best-practices/rules/async-api-routes.md":"---\ntitle: Prevent Waterfall Chains in API Routes\nimpact: CRITICAL\nimpactDescription: 2-10× improvement\ntags: api-routes, server-actions, waterfalls, parallelization\n---\n\n## Prevent Waterfall Chains in API Routes\n\nIn API routes and Server Actions, start independent operations immediately, even if you don't await them yet.\n\n**Incorrect (config waits for auth, data waits for both):**\n\n```typescript\nexport async function GET(request: Request) {\n  const session = await auth()\n  const config = await fetchConfig()\n  const data = await fetchData(session.user.id)\n  return Response.json({ data, config })\n}\n```\n\n**Correct (auth and config start immediately):**\n\n```typescript\nexport async function GET(request: Request) {\n  const sessionPromise = auth()\n  const configPromise = fetchConfig()\n  const session = await sessionPromise\n  const [config, data] = await Promise.all([\n    configPromise,\n    fetchData(session.user.id)\n  ])\n  return Response.json({ data, config })\n}\n```\n\nFor operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization).\n",".agents/skills/vercel-react-best-practices/rules/async-defer-await.md":"---\ntitle: Defer Await Until Needed\nimpact: HIGH\nimpactDescription: avoids blocking unused code paths\ntags: async, await, conditional, optimization\n---\n\n## Defer Await Until Needed\n\nMove `await` operations into the branches where they're actually used to avoid blocking code paths that don't need them.\n\n**Incorrect (blocks both branches):**\n\n```typescript\nasync function handleRequest(userId: string, skipProcessing: boolean) {\n  const userData = await fetchUserData(userId)\n  \n  if (skipProcessing) {\n    // Returns immediately but still waited for userData\n    return { skipped: true }\n  }\n  \n  // Only this branch uses userData\n  return processUserData(userData)\n}\n```\n\n**Correct (only blocks when needed):**\n\n```typescript\nasync function handleRequest(userId: string, skipProcessing: boolean) {\n  if (skipProcessing) {\n    // Returns immediately without waiting\n    return { skipped: true }\n  }\n  \n  // Fetch only when needed\n  const userData = await fetchUserData(userId)\n  return processUserData(userData)\n}\n```\n\n**Another example (early return optimization):**\n\n```typescript\n// Incorrect: always fetches permissions\nasync function updateResource(resourceId: string, userId: string) {\n  const permissions = await fetchPermissions(userId)\n  const resource = await getResource(resourceId)\n  \n  if (!resource) {\n    return { error: 'Not found' }\n  }\n  \n  if (!permissions.canEdit) {\n    return { error: 'Forbidden' }\n  }\n  \n  return await updateResourceData(resource, permissions)\n}\n\n// Correct: fetches only when needed\nasync function updateResource(resourceId: string, userId: string) {\n  const resource = await getResource(resourceId)\n  \n  if (!resource) {\n    return { error: 'Not found' }\n  }\n  \n  const permissions = await fetchPermissions(userId)\n  \n  if (!permissions.canEdit) {\n    return { error: 'Forbidden' }\n  }\n  \n  return await updateResourceData(resource, permissions)\n}\n```\n\nThis optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive.\n",".agents/skills/vercel-react-best-practices/rules/async-dependencies.md":"---\ntitle: Dependency-Based Parallelization\nimpact: CRITICAL\nimpactDescription: 2-10× improvement\ntags: async, parallelization, dependencies, better-all\n---\n\n## Dependency-Based Parallelization\n\nFor operations with partial dependencies, use `better-all` to maximize parallelism. It automatically starts each task at the earliest possible moment.\n\n**Incorrect (profile waits for config unnecessarily):**\n\n```typescript\nconst [user, config] = await Promise.all([\n  fetchUser(),\n  fetchConfig()\n])\nconst profile = await fetchProfile(user.id)\n```\n\n**Correct (config and profile run in parallel):**\n\n```typescript\nimport { all } from 'better-all'\n\nconst { user, config, profile } = await all({\n  async user() { return fetchUser() },\n  async config() { return fetchConfig() },\n  async profile() {\n    return fetchProfile((await this.$.user).id)\n  }\n})\n```\n\n**Alternative without extra dependencies:**\n\nWe can also create all the promises first, and do `Promise.all()` at the end.\n\n```typescript\nconst userPromise = fetchUser()\nconst profilePromise = userPromise.then(user => fetchProfile(user.id))\n\nconst [user, config, profile] = await Promise.all([\n  userPromise,\n  fetchConfig(),\n  profilePromise\n])\n```\n\nReference: [https://github.com/shuding/better-all](https://github.com/shuding/better-all)\n",".agents/skills/vercel-react-best-practices/rules/async-parallel.md":"---\ntitle: Promise.all() for Independent Operations\nimpact: CRITICAL\nimpactDescription: 2-10× improvement\ntags: async, parallelization, promises, waterfalls\n---\n\n## Promise.all() for Independent Operations\n\nWhen async operations have no interdependencies, execute them concurrently using `Promise.all()`.\n\n**Incorrect (sequential execution, 3 round trips):**\n\n```typescript\nconst user = await fetchUser()\nconst posts = await fetchPosts()\nconst comments = await fetchComments()\n```\n\n**Correct (parallel execution, 1 round trip):**\n\n```typescript\nconst [user, posts, comments] = await Promise.all([\n  fetchUser(),\n  fetchPosts(),\n  fetchComments()\n])\n```\n",".agents/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md":"---\ntitle: Strategic Suspense Boundaries\nimpact: HIGH\nimpactDescription: faster initial paint\ntags: async, suspense, streaming, layout-shift\n---\n\n## Strategic Suspense Boundaries\n\nInstead of awaiting data in async components before returning JSX, use Suspense boundaries to show the wrapper UI faster while data loads.\n\n**Incorrect (wrapper blocked by data fetching):**\n\n```tsx\nasync function Page() {\n  const data = await fetchData() // Blocks entire page\n  \n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <div>\n        <DataDisplay data={data} />\n      </div>\n      <div>Footer</div>\n    </div>\n  )\n}\n```\n\nThe entire layout waits for data even though only the middle section needs it.\n\n**Correct (wrapper shows immediately, data streams in):**\n\n```tsx\nfunction Page() {\n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <div>\n        <Suspense fallback={<Skeleton />}>\n          <DataDisplay />\n        </Suspense>\n      </div>\n      <div>Footer</div>\n    </div>\n  )\n}\n\nasync function DataDisplay() {\n  const data = await fetchData() // Only blocks this component\n  return <div>{data.content}</div>\n}\n```\n\nSidebar, Header, and Footer render immediately. Only DataDisplay waits for data.\n\n**Alternative (share promise across components):**\n\n```tsx\nfunction Page() {\n  // Start fetch immediately, but don't await\n  const dataPromise = fetchData()\n  \n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <Suspense fallback={<Skeleton />}>\n        <DataDisplay dataPromise={dataPromise} />\n        <DataSummary dataPromise={dataPromise} />\n      </Suspense>\n      <div>Footer</div>\n    </div>\n  )\n}\n\nfunction DataDisplay({ dataPromise }: { dataPromise: Promise<Data> }) {\n  const data = use(dataPromise) // Unwraps the promise\n  return <div>{data.content}</div>\n}\n\nfunction DataSummary({ dataPromise }: { dataPromise: Promise<Data> }) {\n  const data = use(dataPromise) // Reuses the same promise\n  return <div>{data.summary}</div>\n}\n```\n\nBoth components share the same promise, so only one fetch occurs. Layout renders immediately while both components wait together.\n\n**When NOT to use this pattern:**\n\n- Critical data needed for layout decisions (affects positioning)\n- SEO-critical content above the fold\n- Small, fast queries where suspense overhead isn't worth it\n- When you want to avoid layout shift (loading → content jump)\n\n**Trade-off:** Faster initial paint vs potential layout shift. Choose based on your UX priorities.\n",".agents/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md":"---\ntitle: Avoid Barrel File Imports\nimpact: CRITICAL\nimpactDescription: 200-800ms import cost, slow builds\ntags: bundle, imports, tree-shaking, barrel-files, performance\n---\n\n## Avoid Barrel File Imports\n\nImport directly from source files instead of barrel files to avoid loading thousands of unused modules. **Barrel files** are entry points that re-export multiple modules (e.g., `index.js` that does `export * from './module'`).\n\nPopular icon and component libraries can have **up to 10,000 re-exports** in their entry file. For many React packages, **it takes 200-800ms just to import them**, affecting both development speed and production cold starts.\n\n**Why tree-shaking doesn't help:** When a library is marked as external (not bundled), the bundler can't optimize it. If you bundle it to enable tree-shaking, builds become substantially slower analyzing the entire module graph.\n\n**Incorrect (imports entire library):**\n\n```tsx\nimport { Check, X, Menu } from 'lucide-react'\n// Loads 1,583 modules, takes ~2.8s extra in dev\n// Runtime cost: 200-800ms on every cold start\n\nimport { Button, TextField } from '@mui/material'\n// Loads 2,225 modules, takes ~4.2s extra in dev\n```\n\n**Correct (imports only what you need):**\n\n```tsx\nimport Check from 'lucide-react/dist/esm/icons/check'\nimport X from 'lucide-react/dist/esm/icons/x'\nimport Menu from 'lucide-react/dist/esm/icons/menu'\n// Loads only 3 modules (~2KB vs ~1MB)\n\nimport Button from '@mui/material/Button'\nimport TextField from '@mui/material/TextField'\n// Loads only what you use\n```\n\n**Alternative (Next.js 13.5+):**\n\n```js\n// next.config.js - use optimizePackageImports\nmodule.exports = {\n  experimental: {\n    optimizePackageImports: ['lucide-react', '@mui/material']\n  }\n}\n\n// Then you can keep the ergonomic barrel imports:\nimport { Check, X, Menu } from 'lucide-react'\n// Automatically transformed to direct imports at build time\n```\n\nDirect imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR.\n\nLibraries commonly affected: `lucide-react`, `@mui/material`, `@mui/icons-material`, `@tabler/icons-react`, `react-icons`, `@headlessui/react`, `@radix-ui/react-*`, `lodash`, `ramda`, `date-fns`, `rxjs`, `react-use`.\n\nReference: [How we optimized package imports in Next.js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)\n",".agents/skills/vercel-react-best-practices/rules/bundle-conditional.md":"---\ntitle: Conditional Module Loading\nimpact: HIGH\nimpactDescription: loads large data only when needed\ntags: bundle, conditional-loading, lazy-loading\n---\n\n## Conditional Module Loading\n\nLoad large data or modules only when a feature is activated.\n\n**Example (lazy-load animation frames):**\n\n```tsx\nfunction AnimationPlayer({ enabled, setEnabled }: { enabled: boolean; setEnabled: React.Dispatch<React.SetStateAction<boolean>> }) {\n  const [frames, setFrames] = useState<Frame[] | null>(null)\n\n  useEffect(() => {\n    if (enabled && !frames && typeof window !== 'undefined') {\n      import('./animation-frames.js')\n        .then(mod => setFrames(mod.frames))\n        .catch(() => setEnabled(false))\n    }\n  }, [enabled, frames, setEnabled])\n\n  if (!frames) return <Skeleton />\n  return <Canvas frames={frames} />\n}\n```\n\nThe `typeof window !== 'undefined'` check prevents bundling this module for SSR, optimizing server bundle size and build speed.\n",".agents/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md":"---\ntitle: Defer Non-Critical Third-Party Libraries\nimpact: MEDIUM\nimpactDescription: loads after hydration\ntags: bundle, third-party, analytics, defer\n---\n\n## Defer Non-Critical Third-Party Libraries\n\nAnalytics, logging, and error tracking don't block user interaction. Load them after hydration.\n\n**Incorrect (blocks initial bundle):**\n\n```tsx\nimport { Analytics } from '@vercel/analytics/react'\n\nexport default function RootLayout({ children }) {\n  return (\n    <html>\n      <body>\n        {children}\n        <Analytics />\n      </body>\n    </html>\n  )\n}\n```\n\n**Correct (loads after hydration):**\n\n```tsx\nimport dynamic from 'next/dynamic'\n\nconst Analytics = dynamic(\n  () => import('@vercel/analytics/react').then(m => m.Analytics),\n  { ssr: false }\n)\n\nexport default function RootLayout({ children }) {\n  return (\n    <html>\n      <body>\n        {children}\n        <Analytics />\n      </body>\n    </html>\n  )\n}\n```\n",".agents/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md":"---\ntitle: Dynamic Imports for Heavy Components\nimpact: CRITICAL\nimpactDescription: directly affects TTI and LCP\ntags: bundle, dynamic-import, code-splitting, next-dynamic\n---\n\n## Dynamic Imports for Heavy Components\n\nUse `next/dynamic` to lazy-load large components not needed on initial render.\n\n**Incorrect (Monaco bundles with main chunk ~300KB):**\n\n```tsx\nimport { MonacoEditor } from './monaco-editor'\n\nfunction CodePanel({ code }: { code: string }) {\n  return <MonacoEditor value={code} />\n}\n```\n\n**Correct (Monaco loads on demand):**\n\n```tsx\nimport dynamic from 'next/dynamic'\n\nconst MonacoEditor = dynamic(\n  () => import('./monaco-editor').then(m => m.MonacoEditor),\n  { ssr: false }\n)\n\nfunction CodePanel({ code }: { code: string }) {\n  return <MonacoEditor value={code} />\n}\n```\n",".agents/skills/vercel-react-best-practices/rules/bundle-preload.md":"---\ntitle: Preload Based on User Intent\nimpact: MEDIUM\nimpactDescription: reduces perceived latency\ntags: bundle, preload, user-intent, hover\n---\n\n## Preload Based on User Intent\n\nPreload heavy bundles before they're needed to reduce perceived latency.\n\n**Example (preload on hover/focus):**\n\n```tsx\nfunction EditorButton({ onClick }: { onClick: () => void }) {\n  const preload = () => {\n    if (typeof window !== 'undefined') {\n      void import('./monaco-editor')\n    }\n  }\n\n  return (\n    <button\n      onMouseEnter={preload}\n      onFocus={preload}\n      onClick={onClick}\n    >\n      Open Editor\n    </button>\n  )\n}\n```\n\n**Example (preload when feature flag is enabled):**\n\n```tsx\nfunction FlagsProvider({ children, flags }: Props) {\n  useEffect(() => {\n    if (flags.editorEnabled && typeof window !== 'undefined') {\n      void import('./monaco-editor').then(mod => mod.init())\n    }\n  }, [flags.editorEnabled])\n\n  return <FlagsContext.Provider value={flags}>\n    {children}\n  </FlagsContext.Provider>\n}\n```\n\nThe `typeof window !== 'undefined'` check prevents bundling preloaded modules for SSR, optimizing server bundle size and build speed.\n",".agents/skills/vercel-react-best-practices/rules/client-event-listeners.md":"---\ntitle: Deduplicate Global Event Listeners\nimpact: LOW\nimpactDescription: single listener for N components\ntags: client, swr, event-listeners, subscription\n---\n\n## Deduplicate Global Event Listeners\n\nUse `useSWRSubscription()` to share global event listeners across component instances.\n\n**Incorrect (N instances = N listeners):**\n\n```tsx\nfunction useKeyboardShortcut(key: string, callback: () => void) {\n  useEffect(() => {\n    const handler = (e: KeyboardEvent) => {\n      if (e.metaKey && e.key === key) {\n        callback()\n      }\n    }\n    window.addEventListener('keydown', handler)\n    return () => window.removeEventListener('keydown', handler)\n  }, [key, callback])\n}\n```\n\nWhen using the `useKeyboardShortcut` hook multiple times, each instance will register a new listener.\n\n**Correct (N instances = 1 listener):**\n\n```tsx\nimport useSWRSubscription from 'swr/subscription'\n\n// Module-level Map to track callbacks per key\nconst keyCallbacks = new Map<string, Set<() => void>>()\n\nfunction useKeyboardShortcut(key: string, callback: () => void) {\n  // Register this callback in the Map\n  useEffect(() => {\n    if (!keyCallbacks.has(key)) {\n      keyCallbacks.set(key, new Set())\n    }\n    keyCallbacks.get(key)!.add(callback)\n\n    return () => {\n      const set = keyCallbacks.get(key)\n      if (set) {\n        set.delete(callback)\n        if (set.size === 0) {\n          keyCallbacks.delete(key)\n        }\n      }\n    }\n  }, [key, callback])\n\n  useSWRSubscription('global-keydown', () => {\n    const handler = (e: KeyboardEvent) => {\n      if (e.metaKey && keyCallbacks.has(e.key)) {\n        keyCallbacks.get(e.key)!.forEach(cb => cb())\n      }\n    }\n    window.addEventListener('keydown', handler)\n    return () => window.removeEventListener('keydown', handler)\n  })\n}\n\nfunction Profile() {\n  // Multiple shortcuts will share the same listener\n  useKeyboardShortcut('p', () => { /* ... */ }) \n  useKeyboardShortcut('k', () => { /* ... */ })\n  // ...\n}\n```\n",".agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md":"---\ntitle: Version and Minimize localStorage Data\nimpact: MEDIUM\nimpactDescription: prevents schema conflicts, reduces storage size\ntags: client, localStorage, storage, versioning, data-minimization\n---\n\n## Version and Minimize localStorage Data\n\nAdd version prefix to keys and store only needed fields. Prevents schema conflicts and accidental storage of sensitive data.\n\n**Incorrect:**\n\n```typescript\n// No version, stores everything, no error handling\nlocalStorage.setItem('userConfig', JSON.stringify(fullUserObject))\nconst data = localStorage.getItem('userConfig')\n```\n\n**Correct:**\n\n```typescript\nconst VERSION = 'v2'\n\nfunction saveConfig(config: { theme: string; language: string }) {\n  try {\n    localStorage.setItem(`userConfig:${VERSION}`, JSON.stringify(config))\n  } catch {\n    // Throws in incognito/private browsing, quota exceeded, or disabled\n  }\n}\n\nfunction loadConfig() {\n  try {\n    const data = localStorage.getItem(`userConfig:${VERSION}`)\n    return data ? JSON.parse(data) : null\n  } catch {\n    return null\n  }\n}\n\n// Migration from v1 to v2\nfunction migrate() {\n  try {\n    const v1 = localStorage.getItem('userConfig:v1')\n    if (v1) {\n      const old = JSON.parse(v1)\n      saveConfig({ theme: old.darkMode ? 'dark' : 'light', language: old.lang })\n      localStorage.removeItem('userConfig:v1')\n    }\n  } catch {}\n}\n```\n\n**Store minimal fields from server responses:**\n\n```typescript\n// User object has 20+ fields, only store what UI needs\nfunction cachePrefs(user: FullUser) {\n  try {\n    localStorage.setItem('prefs:v1', JSON.stringify({\n      theme: user.preferences.theme,\n      notifications: user.preferences.notifications\n    }))\n  } catch {}\n}\n```\n\n**Always wrap in try-catch:** `getItem()` and `setItem()` throw in incognito/private browsing (Safari, Firefox), when quota exceeded, or when disabled.\n\n**Benefits:** Schema evolution via versioning, reduced storage size, prevents storing tokens/PII/internal flags.\n",".agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md":"---\ntitle: Use Passive Event Listeners for Scrolling Performance\nimpact: MEDIUM\nimpactDescription: eliminates scroll delay caused by event listeners\ntags: client, event-listeners, scrolling, performance, touch, wheel\n---\n\n## Use Passive Event Listeners for Scrolling Performance\n\nAdd `{ passive: true }` to touch and wheel event listeners to enable immediate scrolling. Browsers normally wait for listeners to finish to check if `preventDefault()` is called, causing scroll delay.\n\n**Incorrect:**\n\n```typescript\nuseEffect(() => {\n  const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)\n  const handleWheel = (e: WheelEvent) => console.log(e.deltaY)\n  \n  document.addEventListener('touchstart', handleTouch)\n  document.addEventListener('wheel', handleWheel)\n  \n  return () => {\n    document.removeEventListener('touchstart', handleTouch)\n    document.removeEventListener('wheel', handleWheel)\n  }\n}, [])\n```\n\n**Correct:**\n\n```typescript\nuseEffect(() => {\n  const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)\n  const handleWheel = (e: WheelEvent) => console.log(e.deltaY)\n  \n  document.addEventListener('touchstart', handleTouch, { passive: true })\n  document.addEventListener('wheel', handleWheel, { passive: true })\n  \n  return () => {\n    document.removeEventListener('touchstart', handleTouch)\n    document.removeEventListener('wheel', handleWheel)\n  }\n}, [])\n```\n\n**Use passive when:** tracking/analytics, logging, any listener that doesn't call `preventDefault()`.\n\n**Don't use passive when:** implementing custom swipe gestures, custom zoom controls, or any listener that needs `preventDefault()`.\n",".agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md":"---\ntitle: Use SWR for Automatic Deduplication\nimpact: MEDIUM-HIGH\nimpactDescription: automatic deduplication\ntags: client, swr, deduplication, data-fetching\n---\n\n## Use SWR for Automatic Deduplication\n\nSWR enables request deduplication, caching, and revalidation across component instances.\n\n**Incorrect (no deduplication, each instance fetches):**\n\n```tsx\nfunction UserList() {\n  const [users, setUsers] = useState([])\n  useEffect(() => {\n    fetch('/api/users')\n      .then(r => r.json())\n      .then(setUsers)\n  }, [])\n}\n```\n\n**Correct (multiple instances share one request):**\n\n```tsx\nimport useSWR from 'swr'\n\nfunction UserList() {\n  const { data: users } = useSWR('/api/users', fetcher)\n}\n```\n\n**For immutable data:**\n\n```tsx\nimport { useImmutableSWR } from '@/lib/swr'\n\nfunction StaticContent() {\n  const { data } = useImmutableSWR('/api/config', fetcher)\n}\n```\n\n**For mutations:**\n\n```tsx\nimport { useSWRMutation } from 'swr/mutation'\n\nfunction UpdateButton() {\n  const { trigger } = useSWRMutation('/api/user', updateUser)\n  return <button onClick={() => trigger()}>Update</button>\n}\n```\n\nReference: [https://swr.vercel.app](https://swr.vercel.app)\n",".agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md":"---\ntitle: Avoid Layout Thrashing\nimpact: MEDIUM\nimpactDescription: prevents forced synchronous layouts and reduces performance bottlenecks\ntags: javascript, dom, css, performance, reflow, layout-thrashing\n---\n\n## Avoid Layout Thrashing\n\nAvoid interleaving style writes with layout reads. When you read a layout property (like `offsetWidth`, `getBoundingClientRect()`, or `getComputedStyle()`) between style changes, the browser is forced to trigger a synchronous reflow.\n\n**This is OK (browser batches style changes):**\n```typescript\nfunction updateElementStyles(element: HTMLElement) {\n  // Each line invalidates style, but browser batches the recalculation\n  element.style.width = '100px'\n  element.style.height = '200px'\n  element.style.backgroundColor = 'blue'\n  element.style.border = '1px solid black'\n}\n```\n\n**Incorrect (interleaved reads and writes force reflows):**\n```typescript\nfunction layoutThrashing(element: HTMLElement) {\n  element.style.width = '100px'\n  const width = element.offsetWidth  // Forces reflow\n  element.style.height = '200px'\n  const height = element.offsetHeight  // Forces another reflow\n}\n```\n\n**Correct (batch writes, then read once):**\n```typescript\nfunction updateElementStyles(element: HTMLElement) {\n  // Batch all writes together\n  element.style.width = '100px'\n  element.style.height = '200px'\n  element.style.backgroundColor = 'blue'\n  element.style.border = '1px solid black'\n  \n  // Read after all writes are done (single reflow)\n  const { width, height } = element.getBoundingClientRect()\n}\n```\n\n**Correct (batch reads, then writes):**\n```typescript\nfunction avoidThrashing(element: HTMLElement) {\n  // Read phase - all layout queries first\n  const rect1 = element.getBoundingClientRect()\n  const offsetWidth = element.offsetWidth\n  const offsetHeight = element.offsetHeight\n  \n  // Write phase - all style changes after\n  element.style.width = '100px'\n  element.style.height = '200px'\n}\n```\n\n**Better: use CSS classes**\n```css\n.highlighted-box {\n  width: 100px;\n  height: 200px;\n  background-color: blue;\n  border: 1px solid black;\n}\n```\n```typescript\nfunction updateElementStyles(element: HTMLElement) {\n  element.classList.add('highlighted-box')\n  \n  const { width, height } = element.getBoundingClientRect()\n}\n```\n\n**React example:**\n```tsx\n// Incorrect: interleaving style changes with layout queries\nfunction Box({ isHighlighted }: { isHighlighted: boolean }) {\n  const ref = useRef<HTMLDivElement>(null)\n  \n  useEffect(() => {\n    if (ref.current && isHighlighted) {\n      ref.current.style.width = '100px'\n      const width = ref.current.offsetWidth // Forces layout\n      ref.current.style.height = '200px'\n    }\n  }, [isHighlighted])\n  \n  return <div ref={ref}>Content</div>\n}\n\n// Correct: toggle class\nfunction Box({ isHighlighted }: { isHighlighted: boolean }) {\n  return (\n    <div className={isHighlighted ? 'highlighted-box' : ''}>\n      Content\n    </div>\n  )\n}\n```\n\nPrefer CSS classes over inline styles when possible. CSS files are cached by the browser, and classes provide better separation of concerns and are easier to maintain.\n\nSee [this gist](https://gist.github.com/paulirish/5d52fb081b3570c81e3a) and [CSS Triggers](https://csstriggers.com/) for more information on layout-forcing operations.\n",".agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md":"---\ntitle: Cache Repeated Function Calls\nimpact: MEDIUM\nimpactDescription: avoid redundant computation\ntags: javascript, cache, memoization, performance\n---\n\n## Cache Repeated Function Calls\n\nUse a module-level Map to cache function results when the same function is called repeatedly with the same inputs during render.\n\n**Incorrect (redundant computation):**\n\n```typescript\nfunction ProjectList({ projects }: { projects: Project[] }) {\n  return (\n    <div>\n      {projects.map(project => {\n        // slugify() called 100+ times for same project names\n        const slug = slugify(project.name)\n        \n        return <ProjectCard key={project.id} slug={slug} />\n      })}\n    </div>\n  )\n}\n```\n\n**Correct (cached results):**\n\n```typescript\n// Module-level cache\nconst slugifyCache = new Map<string, string>()\n\nfunction cachedSlugify(text: string): string {\n  if (slugifyCache.has(text)) {\n    return slugifyCache.get(text)!\n  }\n  const result = slugify(text)\n  slugifyCache.set(text, result)\n  return result\n}\n\nfunction ProjectList({ projects }: { projects: Project[] }) {\n  return (\n    <div>\n      {projects.map(project => {\n        // Computed only once per unique project name\n        const slug = cachedSlugify(project.name)\n        \n        return <ProjectCard key={project.id} slug={slug} />\n      })}\n    </div>\n  )\n}\n```\n\n**Simpler pattern for single-value functions:**\n\n```typescript\nlet isLoggedInCache: boolean | null = null\n\nfunction isLoggedIn(): boolean {\n  if (isLoggedInCache !== null) {\n    return isLoggedInCache\n  }\n  \n  isLoggedInCache = document.cookie.includes('auth=')\n  return isLoggedInCache\n}\n\n// Clear cache when auth changes\nfunction onAuthChange() {\n  isLoggedInCache = null\n}\n```\n\nUse a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.\n\nReference: [How we made the Vercel Dashboard twice as fast](https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast)\n",".agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md":"---\ntitle: Cache Property Access in Loops\nimpact: LOW-MEDIUM\nimpactDescription: reduces lookups\ntags: javascript, loops, optimization, caching\n---\n\n## Cache Property Access in Loops\n\nCache object property lookups in hot paths.\n\n**Incorrect (3 lookups × N iterations):**\n\n```typescript\nfor (let i = 0; i < arr.length; i++) {\n  process(obj.config.settings.value)\n}\n```\n\n**Correct (1 lookup total):**\n\n```typescript\nconst value = obj.config.settings.value\nconst len = arr.length\nfor (let i = 0; i < len; i++) {\n  process(value)\n}\n```\n",".agents/skills/vercel-react-best-practices/rules/js-cache-storage.md":"---\ntitle: Cache Storage API Calls\nimpact: LOW-MEDIUM\nimpactDescription: reduces expensive I/O\ntags: javascript, localStorage, storage, caching, performance\n---\n\n## Cache Storage API Calls\n\n`localStorage`, `sessionStorage`, and `document.cookie` are synchronous and expensive. Cache reads in memory.\n\n**Incorrect (reads storage on every call):**\n\n```typescript\nfunction getTheme() {\n  return localStorage.getItem('theme') ?? 'light'\n}\n// Called 10 times = 10 storage reads\n```\n\n**Correct (Map cache):**\n\n```typescript\nconst storageCache = new Map<string, string | null>()\n\nfunction getLocalStorage(key: string) {\n  if (!storageCache.has(key)) {\n    storageCache.set(key, localStorage.getItem(key))\n  }\n  return storageCache.get(key)\n}\n\nfunction setLocalStorage(key: string, value: string) {\n  localStorage.setItem(key, value)\n  storageCache.set(key, value)  // keep cache in sync\n}\n```\n\nUse a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.\n\n**Cookie caching:**\n\n```typescript\nlet cookieCache: Record<string, string> | null = null\n\nfunction getCookie(name: string) {\n  if (!cookieCache) {\n    cookieCache = Object.fromEntries(\n      document.cookie.split('; ').map(c => c.split('='))\n    )\n  }\n  return cookieCache[name]\n}\n```\n\n**Important (invalidate on external changes):**\n\nIf storage can change externally (another tab, server-set cookies), invalidate cache:\n\n```typescript\nwindow.addEventListener('storage', (e) => {\n  if (e.key) storageCache.delete(e.key)\n})\n\ndocument.addEventListener('visibilitychange', () => {\n  if (document.visibilityState === 'visible') {\n    storageCache.clear()\n  }\n})\n```\n",".agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md":"---\ntitle: Combine Multiple Array Iterations\nimpact: LOW-MEDIUM\nimpactDescription: reduces iterations\ntags: javascript, arrays, loops, performance\n---\n\n## Combine Multiple Array Iterations\n\nMultiple `.filter()` or `.map()` calls iterate the array multiple times. Combine into one loop.\n\n**Incorrect (3 iterations):**\n\n```typescript\nconst admins = users.filter(u => u.isAdmin)\nconst testers = users.filter(u => u.isTester)\nconst inactive = users.filter(u => !u.isActive)\n```\n\n**Correct (1 iteration):**\n\n```typescript\nconst admins: User[] = []\nconst testers: User[] = []\nconst inactive: User[] = []\n\nfor (const user of users) {\n  if (user.isAdmin) admins.push(user)\n  if (user.isTester) testers.push(user)\n  if (!user.isActive) inactive.push(user)\n}\n```\n",".agents/skills/vercel-react-best-practices/rules/js-early-exit.md":"---\ntitle: Early Return from Functions\nimpact: LOW-MEDIUM\nimpactDescription: avoids unnecessary computation\ntags: javascript, functions, optimization, early-return\n---\n\n## Early Return from Functions\n\nReturn early when result is determined to skip unnecessary processing.\n\n**Incorrect (processes all items even after finding answer):**\n\n```typescript\nfunction validateUsers(users: User[]) {\n  let hasError = false\n  let errorMessage = ''\n  \n  for (const user of users) {\n    if (!user.email) {\n      hasError = true\n      errorMessage = 'Email required'\n    }\n    if (!user.name) {\n      hasError = true\n      errorMessage = 'Name required'\n    }\n    // Continues checking all users even after error found\n  }\n  \n  return hasError ? { valid: false, error: errorMessage } : { valid: true }\n}\n```\n\n**Correct (returns immediately on first error):**\n\n```typescript\nfunction validateUsers(users: User[]) {\n  for (const user of users) {\n    if (!user.email) {\n      return { valid: false, error: 'Email required' }\n    }\n    if (!user.name) {\n      return { valid: false, error: 'Name required' }\n    }\n  }\n\n  return { valid: true }\n}\n```\n",".agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md":"---\ntitle: Hoist RegExp Creation\nimpact: LOW-MEDIUM\nimpactDescription: avoids recreation\ntags: javascript, regexp, optimization, memoization\n---\n\n## Hoist RegExp Creation\n\nDon't create RegExp inside render. Hoist to module scope or memoize with `useMemo()`.\n\n**Incorrect (new RegExp every render):**\n\n```tsx\nfunction Highlighter({ text, query }: Props) {\n  const regex = new RegExp(`(${query})`, 'gi')\n  const parts = text.split(regex)\n  return <>{parts.map((part, i) => ...)}</>\n}\n```\n\n**Correct (memoize or hoist):**\n\n```tsx\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\nfunction Highlighter({ text, query }: Props) {\n  const regex = useMemo(\n    () => new RegExp(`(${escapeRegex(query)})`, 'gi'),\n    [query]\n  )\n  const parts = text.split(regex)\n  return <>{parts.map((part, i) => ...)}</>\n}\n```\n\n**Warning (global regex has mutable state):**\n\nGlobal regex (`/g`) has mutable `lastIndex` state:\n\n```typescript\nconst regex = /foo/g\nregex.test('foo')  // true, lastIndex = 3\nregex.test('foo')  // false, lastIndex = 0\n```\n",".agents/skills/vercel-react-best-practices/rules/js-index-maps.md":"---\ntitle: Build Index Maps for Repeated Lookups\nimpact: LOW-MEDIUM\nimpactDescription: 1M ops to 2K ops\ntags: javascript, map, indexing, optimization, performance\n---\n\n## Build Index Maps for Repeated Lookups\n\nMultiple `.find()` calls by the same key should use a Map.\n\n**Incorrect (O(n) per lookup):**\n\n```typescript\nfunction processOrders(orders: Order[], users: User[]) {\n  return orders.map(order => ({\n    ...order,\n    user: users.find(u => u.id === order.userId)\n  }))\n}\n```\n\n**Correct (O(1) per lookup):**\n\n```typescript\nfunction processOrders(orders: Order[], users: User[]) {\n  const userById = new Map(users.map(u => [u.id, u]))\n\n  return orders.map(order => ({\n    ...order,\n    user: userById.get(order.userId)\n  }))\n}\n```\n\nBuild map once (O(n)), then all lookups are O(1).\nFor 1000 orders × 1000 users: 1M ops → 2K ops.\n",".agents/skills/vercel-react-best-practices/rules/js-length-check-first.md":"---\ntitle: Early Length Check for Array Comparisons\nimpact: MEDIUM-HIGH\nimpactDescription: avoids expensive operations when lengths differ\ntags: javascript, arrays, performance, optimization, comparison\n---\n\n## Early Length Check for Array Comparisons\n\nWhen comparing arrays with expensive operations (sorting, deep equality, serialization), check lengths first. If lengths differ, the arrays cannot be equal.\n\nIn real-world applications, this optimization is especially valuable when the comparison runs in hot paths (event handlers, render loops).\n\n**Incorrect (always runs expensive comparison):**\n\n```typescript\nfunction hasChanges(current: string[], original: string[]) {\n  // Always sorts and joins, even when lengths differ\n  return current.sort().join() !== original.sort().join()\n}\n```\n\nTwo O(n log n) sorts run even when `current.length` is 5 and `original.length` is 100. There is also overhead of joining the arrays and comparing the strings.\n\n**Correct (O(1) length check first):**\n\n```typescript\nfunction hasChanges(current: string[], original: string[]) {\n  // Early return if lengths differ\n  if (current.length !== original.length) {\n    return true\n  }\n  // Only sort when lengths match\n  const currentSorted = current.toSorted()\n  const originalSorted = original.toSorted()\n  for (let i = 0; i < currentSorted.length; i++) {\n    if (currentSorted[i] !== originalSorted[i]) {\n      return true\n    }\n  }\n  return false\n}\n```\n\nThis new approach is more efficient because:\n- It avoids the overhead of sorting and joining the arrays when lengths differ\n- It avoids consuming memory for the joined strings (especially important for large arrays)\n- It avoids mutating the original arrays\n- It returns early when a difference is found\n",".agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md":"---\ntitle: Use Loop for Min/Max Instead of Sort\nimpact: LOW\nimpactDescription: O(n) instead of O(n log n)\ntags: javascript, arrays, performance, sorting, algorithms\n---\n\n## Use Loop for Min/Max Instead of Sort\n\nFinding the smallest or largest element only requires a single pass through the array. Sorting is wasteful and slower.\n\n**Incorrect (O(n log n) - sort to find latest):**\n\n```typescript\ninterface Project {\n  id: string\n  name: string\n  updatedAt: number\n}\n\nfunction getLatestProject(projects: Project[]) {\n  const sorted = [...projects].sort((a, b) => b.updatedAt - a.updatedAt)\n  return sorted[0]\n}\n```\n\nSorts the entire array just to find the maximum value.\n\n**Incorrect (O(n log n) - sort for oldest and newest):**\n\n```typescript\nfunction getOldestAndNewest(projects: Project[]) {\n  const sorted = [...projects].sort((a, b) => a.updatedAt - b.updatedAt)\n  return { oldest: sorted[0], newest: sorted[sorted.length - 1] }\n}\n```\n\nStill sorts unnecessarily when only min/max are needed.\n\n**Correct (O(n) - single loop):**\n\n```typescript\nfunction getLatestProject(projects: Project[]) {\n  if (projects.length === 0) return null\n  \n  let latest = projects[0]\n  \n  for (let i = 1; i < projects.length; i++) {\n    if (projects[i].updatedAt > latest.updatedAt) {\n      latest = projects[i]\n    }\n  }\n  \n  return latest\n}\n\nfunction getOldestAndNewest(projects: Project[]) {\n  if (projects.length === 0) return { oldest: null, newest: null }\n  \n  let oldest = projects[0]\n  let newest = projects[0]\n  \n  for (let i = 1; i < projects.length; i++) {\n    if (projects[i].updatedAt < oldest.updatedAt) oldest = projects[i]\n    if (projects[i].updatedAt > newest.updatedAt) newest = projects[i]\n  }\n  \n  return { oldest, newest }\n}\n```\n\nSingle pass through the array, no copying, no sorting.\n\n**Alternative (Math.min/Math.max for small arrays):**\n\n```typescript\nconst numbers = [5, 2, 8, 1, 9]\nconst min = Math.min(...numbers)\nconst max = Math.max(...numbers)\n```\n\nThis works for small arrays, but can be slower or just throw an error for very large arrays due to spread operator limitations. Maximal array length is approximately 124000 in Chrome 143 and 638000 in Safari 18; exact numbers may vary - see [the fiddle](https://jsfiddle.net/qw1jabsx/4/). Use the loop approach for reliability.\n",".agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-set-map-lookups.md -->",".agents/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-tosorted-immutable.md -->",".agents/skills/vercel-react-best-practices/rules/rendering-activity.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-activity.md -->",".agents/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-animate-svg-wrapper.md -->",".agents/skills/vercel-react-best-practices/rules/rendering-conditional-render.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-conditional-render.md -->",".agents/skills/vercel-react-best-practices/rules/rendering-content-visibility.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-content-visibility.md -->",".agents/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-hoist-jsx.md -->",".agents/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-hydration-no-flicker.md -->",".agents/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-hydration-suppress-warning.md -->",".agents/skills/vercel-react-best-practices/rules/rendering-svg-precision.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-svg-precision.md -->",".agents/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-usetransition-loading.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-defer-reads.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-defer-reads.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-dependencies.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-dependencies.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-derived-state-no-effect.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-derived-state.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-derived-state.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-functional-setstate.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-lazy-state-init.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-memo-with-default-value.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-memo.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-memo.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-move-effect-to-event.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-simple-expression-in-memo.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-transitions.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-transitions.md -->",".agents/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-use-ref-transient-values.md -->",".agents/skills/vercel-react-best-practices/rules/server-after-nonblocking.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-after-nonblocking.md -->",".agents/skills/vercel-react-best-practices/rules/server-auth-actions.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-auth-actions.md -->",".agents/skills/vercel-react-best-practices/rules/server-cache-lru.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-cache-lru.md -->",".agents/skills/vercel-react-best-practices/rules/server-cache-react.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-cache-react.md -->",".agents/skills/vercel-react-best-practices/rules/server-dedup-props.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-dedup-props.md -->",".agents/skills/vercel-react-best-practices/rules/server-hoist-static-io.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-hoist-static-io.md -->",".agents/skills/vercel-react-best-practices/rules/server-parallel-fetching.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-parallel-fetching.md -->",".agents/skills/vercel-react-best-practices/rules/server-serialization.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-serialization.md -->",".agents/skills/web-animation-design/PRACTICAL-TIPS.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fweb-animation-design%2FPRACTICAL-TIPS.md -->",".agents/skills/web-animation-design/SKILL.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fweb-animation-design%2FSKILL.md -->",".agents/skills/workflow/SKILL.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fworkflow%2FSKILL.md -->","AGENTS.md":"# AGENTS.md\n\nThis file provides guidance for AI coding agents working in this repository.\n\n**This is a living document.** When you make a mistake or learn something new about this codebase, add it to [Lessons Learned](docs/agents/lessons-learned.md).\n\n## Quick Links\n\n- [Architecture & Workspace Structure](docs/agents/architecture.md)\n- [Code Style & Patterns](docs/agents/code-style.md)\n- [Lessons Learned](docs/agents/lessons-learned.md)\n\n## Authentication\n\nAuthentication uses [Better Auth](https://www.better-auth.com/) with Vercel OAuth (sign-in) and GitHub OAuth (repo access). Config lives in `apps/web/lib/auth/config.ts`. Sessions are managed by better-auth's built-in session system — there is no manual JWE/encryption layer.\n\nKey env vars: `BETTER_AUTH_SECRET` (session signing), `NEXT_PUBLIC_VERCEL_APP_CLIENT_ID` + `VERCEL_APP_CLIENT_SECRET` (Vercel OAuth), plus GitHub App credentials for repo access. See `apps/web/.env.example` for the full list.\n\n## Database & Migrations\n\nSchema lives in `apps/web/lib/db/schema.ts`. Migrations are managed by Drizzle Kit.\n\n**After modifying `schema.ts`, always generate a migration:**\n\n```bash\npnpm --dir apps/web db:generate   # Creates a new .sql migration file\n```\n\nCommit the generated `.sql` file alongside the schema change. **Do not use `db:push`** except for local throwaway databases.\n\nMigrations run automatically during `pnpm build` (via `lib/db/migrate.ts`), so every Vercel deploy — both preview and production — applies pending migrations to its own database.\n\n### Environment isolation\n\nNeon database branching is enabled in the Vercel project settings. Every preview deployment automatically gets its own isolated database branch forked from production. This means preview deployments never read or write production data. Production deployments use the main Neon database.\n\n## Commands\n\n```bash\n# Development\npnpm web            # Run web app\n\n# Quality checks (REQUIRED after making any changes)\npnpm run ci                             # Required: run format check, lint, typecheck, and tests\nturbo typecheck                            # Type check all packages\n\n# Linting and formatting (Ultracite - oxlint + oxfmt, run from root)\npnpm check                              # Lint and format check all files\npnpm fix                                # Lint fix and format all files\n\n# Filter by package (use --filter)\nturbo typecheck --filter=web # Type check web app only\n\n# Testing\nbun test                                              # Run all tests\nbun test path/to/file.test.ts                         # Run single test file\nbun test --watch                                      # Watch mode\npnpm test:verbose                                  # Run tests with JUnit reporter streamed to stdout (useful in non-interactive shells)\npnpm test:verbose path/to/file.test.ts             # Same verbose output for a single test file\n```\n\n**CI/script execution rules:**\n\n- Run project checks through package scripts (for example `pnpm run ci`, `pnpm --dir apps/web db:check`).\n- Prefer `pnpm <script>` over invoking tool binaries directly (`pnpm exec`, `tsc`, `eslint`, etc.) so local runs match CI behavior.\n\n## Git Commands\n\n- **Branch sync preference:** When bringing in `origin/main`, prefer a normal merge (`git fetch origin main` then `git merge origin/main`) instead of rebasing, unless explicitly requested otherwise.\n\n**Quote paths with special characters**: File paths containing brackets (like Next.js dynamic routes `[id]`, `[slug]`) are interpreted as glob patterns by zsh. Always quote these paths in git commands:\n\n```bash\n# Wrong - zsh interprets [id] as a glob pattern\ngit add apps/web/app/tasks/[id]/page.tsx\n# Error: no matches found: apps/web/app/tasks/[id]/page.tsx\n\n# Correct - quote the path\ngit add \"apps/web/app/tasks/[id]/page.tsx\"\n```\n\n## Architecture (Summary)\n\n```\nWeb -> Agent (packages/agent) -> Sandbox (packages/sandbox)\n```\n\nSee [Architecture & Workspace Structure](docs/agents/architecture.md) for details.\n\n## File Organization & Separation of Concerns\n\n- Do **not** append new functionality to the bottom of an existing file by default.\n- Before adding code, decide whether the behavior is a separate concern that should live in its own file.\n- Prefer creating a new colocated file for distinct concerns (components, hooks, utilities, schemas, data-access helpers, etc.).\n- If a file is already large or handling multiple responsibilities, extract the new logic (and related helpers/types) into focused modules and import them.\n- For large page/view/client components, default to adding new feature behavior in colocated hooks and colocated child components instead of growing the main file.\n- If a change introduces a distinct cluster of state, effects, handlers, API calls, or derived UI labels for one feature, treat that as a strong signal to extract it.\n- Keep each file focused on one primary responsibility; avoid mixing unrelated UI, business logic, and data-access code in the same file.\n\n## Code Style (Summary)\n\n- **pnpm exclusively for dependency management**; use Node 24 for utility scripts and Bun for tests\n- **Files**: kebab-case, **Types**: PascalCase, **Functions**: camelCase\n- **Never use `any`** -- use `unknown` and narrow with type guards\n- **No `.js` extensions** in imports\n- **Ultracite** (oxlint + oxfmt) for linting and formatting (double quotes, 2-space indent)\n- **Zod** schemas for validation, derive types with `z.infer`\n\nSee [Code Style & Patterns](docs/agents/code-style.md) for full conventions, tool implementation patterns, and dependency patterns.\n","CLAUDE.md":"AGENTS.md","docs/agents/architecture.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=docs%2Fagents%2Farchitecture.md -->","docs/agents/code-style.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=docs%2Fagents%2Fcode-style.md -->","docs/agents/lessons-learned.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=docs%2Fagents%2Flessons-learned.md -->","docs/agents/react-best-practices-audit.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=docs%2Fagents%2Freact-best-practices-audit.md -->","packages/agent/docs/approval-system.md":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=packages%2Fagent%2Fdocs%2Fapproval-system.md -->"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md\n\nThis file provides guidance for AI coding agents working in this repository.\n\n**This is a living document.** When you make a mistake or learn something new about this codebase, add it to [Lessons Learned](docs/agents/lessons-learned.md).\n\n## Quick Links\n\n- [Architecture & Workspace Structure](docs/agents/architecture.md)\n- [Code Style & Patterns](docs/agents/code-style.md)\n- [Lessons Learned](docs/agents/lessons-learned.md)\n\n## Authentication\n\nAuthentication uses [Better Auth](https://www.better-auth.com/) with Vercel OAuth (sign-in) and GitHub OAuth (repo access). Config lives in `apps/web/lib/auth/config.ts`. Sessions are managed by better-auth's built-in session system — there is no manual JWE/encryption layer.\n\nKey env vars: `BETTER_AUTH_SECRET` (session signing), `NEXT_PUBLIC_VERCEL_APP_CLIENT_ID` + `VERCEL_APP_CLIENT_SECRET` (Vercel OAuth), plus GitHub App credentials for repo access. See `apps/web/.env.example` for the full list.\n\n## Database & Migrations\n\nSchema lives in `apps/web/lib/db/schema.ts`. Migrations are managed by Drizzle Kit.\n\n**After modifying `schema.ts`, always generate a migration:**\n\n```bash\npnpm --dir apps/web db:generate   # Creates a new .sql migration file\n```\n\nCommit the generated `.sql` file alongside the schema change. **Do not use `db:push`** except for local throwaway databases.\n\nMigrations run automatically during `pnpm build` (via `lib/db/migrate.ts`), so every Vercel deploy — both preview and production — applies pending migrations to its own database.\n\n### Environment isolation\n\nNeon database branching is enabled in the Vercel project settings. Every preview deployment automatically gets its own isolated database branch forked from production. This means preview deployments never read or write production data. Production deployments use the main Neon database.\n\n## Commands\n\n```bash\n# Development\npnpm web            # Run web app\n\n# Quality checks (REQUIRED after making any changes)\npnpm run ci                             # Required: run format check, lint, typecheck, and tests\nturbo typecheck                            # Type check all packages\n\n# Linting and formatting (Ultracite - oxlint + oxfmt, run from root)\npnpm check                              # Lint and format check all files\npnpm fix                                # Lint fix and format all files\n\n# Filter by package (use --filter)\nturbo typecheck --filter=web # Type check web app only\n\n# Testing\nbun test                                              # Run all tests\nbun test path/to/file.test.ts                         # Run single test file\nbun test --watch                                      # Watch mode\npnpm test:verbose                                  # Run tests with JUnit reporter streamed to stdout (useful in non-interactive shells)\npnpm test:verbose path/to/file.test.ts             # Same verbose output for a single test file\n```\n\n**CI/script execution rules:**\n\n- Run project checks through package scripts (for example `pnpm run ci`, `pnpm --dir apps/web db:check`).\n- Prefer `pnpm <script>` over invoking tool binaries directly (`pnpm exec`, `tsc`, `eslint`, etc.) so local runs match CI behavior.\n\n## Git Commands\n\n- **Branch sync preference:** When bringing in `origin/main`, prefer a normal merge (`git fetch origin main` then `git merge origin/main`) instead of rebasing, unless explicitly requested otherwise.\n\n**Quote paths with special characters**: File paths containing brackets (like Next.js dynamic routes `[id]`, `[slug]`) are interpreted as glob patterns by zsh. Always quote these paths in git commands:\n\n```bash\n# Wrong - zsh interprets [id] as a glob pattern\ngit add apps/web/app/tasks/[id]/page.tsx\n# Error: no matches found: apps/web/app/tasks/[id]/page.tsx\n\n# Correct - quote the path\ngit add \"apps/web/app/tasks/[id]/page.tsx\"\n```\n\n## Architecture (Summary)\n\n```\nWeb -> Agent (packages/agent) -> Sandbox (packages/sandbox)\n```\n\nSee [Architecture & Workspace Structure](docs/agents/architecture.md) for details.\n\n## File Organization & Separation of Concerns\n\n- Do **not** append new functionality to the bottom of an existing file by default.\n- Before adding code, decide whether the behavior is a separate concern that should live in its own file.\n- Prefer creating a new colocated file for distinct concerns (components, hooks, utilities, schemas, data-access helpers, etc.).\n- If a file is already large or handling multiple responsibilities, extract the new logic (and related helpers/types) into focused modules and import them.\n- For large page/view/client components, default to adding new feature behavior in colocated hooks and colocated child components instead of growing the main file.\n- If a change introduces a distinct cluster of state, effects, handlers, API calls, or derived UI labels for one feature, treat that as a strong signal to extract it.\n- Keep each file focused on one primary responsibility; avoid mixing unrelated UI, business logic, and data-access code in the same file.\n\n## Code Style (Summary)\n\n- **pnpm exclusively for dependency management**; use Node 24 for utility scripts and Bun for tests\n- **Files**: kebab-case, **Types**: PascalCase, **Functions**: camelCase\n- **Never use `any`** -- use `unknown` and narrow with type guards\n- **No `.js` extensions** in imports\n- **Ultracite** (oxlint + oxfmt) for linting and formatting (double quotes, 2-space indent)\n- **Zod** schemas for validation, derive types with `z.infer`\n\nSee [Code Style & Patterns](docs/agents/code-style.md) for full conventions, tool implementation patterns, and dependency patterns.\n","isInternal":false,"tokens":1248,"sizeBytes":5593},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"AGENTS.md","isInternal":false,"tokens":3,"sizeBytes":9},{"name":"authentication.md","path":".agents/skills/agent-browser/references/authentication.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/agent-browser/references/authentication.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Authentication Patterns\n\nPatterns for handling login flows, session persistence, and authenticated browsing.\n\n## Basic Login Flow\n\n```bash\n# Navigate to login page\nagent-browser open https://app.example.com/login\nagent-browser wait --load networkidle\n\n# Get form elements\nagent-browser snapshot -i\n# Output: @e1 [input type=\"email\"], @e2 [input type=\"password\"], @e3 [button] \"Sign In\"\n\n# Fill credentials\nagent-browser fill @e1 \"user@example.com\"\nagent-browser fill @e2 \"password123\"\n\n# Submit\nagent-browser click @e3\nagent-browser wait --load networkidle\n\n# Verify login succeeded\nagent-browser get url  # Should be dashboard, not login\n```\n\n## Saving Authentication State\n\nAfter logging in, save state for reuse:\n\n```bash\n# Login first (see above)\nagent-browser open https://app.example.com/login\nagent-browser snapshot -i\nagent-browser fill @e1 \"user@example.com\"\nagent-browser fill @e2 \"password123\"\nagent-browser click @e3\nagent-browser wait --url \"**/dashboard\"\n\n# Save authenticated state\nagent-browser state save ./auth-state.json\n```\n\n## Restoring Authentication\n\nSkip login by loading saved state:\n\n```bash\n# Load saved auth state\nagent-browser state load ./auth-state.json\n\n# Navigate directly to protected page\nagent-browser open https://app.example.com/dashboard\n\n# Verify authenticated\nagent-browser snapshot -i\n```\n\n## OAuth / SSO Flows\n\nFor OAuth redirects:\n\n```bash\n# Start OAuth flow\nagent-browser open https://app.example.com/auth/google\n\n# Handle redirects automatically\nagent-browser wait --url \"**/accounts.google.com**\"\nagent-browser snapshot -i\n\n# Fill Google credentials\nagent-browser fill @e1 \"user@gmail.com\"\nagent-browser click @e2  # Next button\nagent-browser wait 2000\nagent-browser snapshot -i\nagent-browser fill @e3 \"password\"\nagent-browser click @e4  # Sign in\n\n# Wait for redirect back\nagent-browser wait --url \"**/app.example.com**\"\nagent-browser state save ./oauth-state.json\n```\n\n## Two-Factor Authentication\n\nHandle 2FA with manual intervention:\n\n```bash\n# Login with credentials\nagent-browser open https://app.example.com/login --headed  # Show browser\nagent-browser snapshot -i\nagent-browser fill @e1 \"user@example.com\"\nagent-browser fill @e2 \"password123\"\nagent-browser click @e3\n\n# Wait for user to complete 2FA manually\necho \"Complete 2FA in the browser window...\"\nagent-browser wait --url \"**/dashboard\" --timeout 120000\n\n# Save state after 2FA\nagent-browser state save ./2fa-state.json\n```\n\n## HTTP Basic Auth\n\nFor sites using HTTP Basic Authentication:\n\n```bash\n# Set credentials before navigation\nagent-browser set credentials username password\n\n# Navigate to protected resource\nagent-browser open https://protected.example.com/api\n```\n\n## Cookie-Based Auth\n\nManually set authentication cookies:\n\n```bash\n# Set auth cookie\nagent-browser cookies set session_token \"abc123xyz\"\n\n# Navigate to protected page\nagent-browser open https://app.example.com/dashboard\n```\n\n## Token Refresh Handling\n\nFor sessions with expiring tokens:\n\n```bash\n#!/bin/bash\n# Wrapper that handles token refresh\n\nSTATE_FILE=\"./auth-state.json\"\n\n# Try loading existing state\nif [[ -f \"$STATE_FILE\" ]]; then\n    agent-browser state load \"$STATE_FILE\"\n    agent-browser open https://app.example.com/dashboard\n\n    # Check if session is still valid\n    URL=$(agent-browser get url)\n    if [[ \"$URL\" == *\"/login\"* ]]; then\n        echo \"Session expired, re-authenticating...\"\n        # Perform fresh login\n        agent-browser snapshot -i\n        agent-browser fill @e1 \"$USERNAME\"\n        agent-browser fill @e2 \"$PASSWORD\"\n        agent-browser click @e3\n        agent-browser wait --url \"**/dashboard\"\n        agent-browser state save \"$STATE_FILE\"\n    fi\nelse\n    # First-time login\n    agent-browser open https://app.example.com/login\n    # ... login flow ...\nfi\n```\n\n## Security Best Practices\n\n1. **Never commit state files** - They contain session tokens\n   ```bash\n   echo \"*.auth-state.json\" >> .gitignore\n   ```\n\n2. **Use environment variables for credentials**\n   ```bash\n   agent-browser fill @e1 \"$APP_USERNAME\"\n   agent-browser fill @e2 \"$APP_PASSWORD\"\n   ```\n\n3. **Clean up after automation**\n   ```bash\n   agent-browser cookies clear\n   rm -f ./auth-state.json\n   ```\n\n4. **Use short-lived sessions for CI/CD**\n   ```bash\n   # Don't persist state in CI\n   agent-browser open https://app.example.com/login\n   # ... login and perform actions ...\n   agent-browser close  # Session ends, nothing persisted\n   ```\n","isInternal":false,"tokens":1040,"sizeBytes":4443},{"name":"proxy-support.md","path":".agents/skills/agent-browser/references/proxy-support.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/agent-browser/references/proxy-support.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Proxy Support\n\nConfigure proxy servers for browser automation, useful for geo-testing, rate limiting avoidance, and corporate environments.\n\n## Basic Proxy Configuration\n\nSet proxy via environment variable before starting:\n\n```bash\n# HTTP proxy\nexport HTTP_PROXY=\"http://proxy.example.com:8080\"\nagent-browser open https://example.com\n\n# HTTPS proxy\nexport HTTPS_PROXY=\"https://proxy.example.com:8080\"\nagent-browser open https://example.com\n\n# Both\nexport HTTP_PROXY=\"http://proxy.example.com:8080\"\nexport HTTPS_PROXY=\"http://proxy.example.com:8080\"\nagent-browser open https://example.com\n```\n\n## Authenticated Proxy\n\nFor proxies requiring authentication:\n\n```bash\n# Include credentials in URL\nexport HTTP_PROXY=\"http://username:password@proxy.example.com:8080\"\nagent-browser open https://example.com\n```\n\n## SOCKS Proxy\n\n```bash\n# SOCKS5 proxy\nexport ALL_PROXY=\"socks5://proxy.example.com:1080\"\nagent-browser open https://example.com\n\n# SOCKS5 with auth\nexport ALL_PROXY=\"socks5://user:pass@proxy.example.com:1080\"\nagent-browser open https://example.com\n```\n\n## Proxy Bypass\n\nSkip proxy for specific domains:\n\n```bash\n# Bypass proxy for local addresses\nexport NO_PROXY=\"localhost,127.0.0.1,.internal.company.com\"\nagent-browser open https://internal.company.com  # Direct connection\nagent-browser open https://external.com          # Via proxy\n```\n\n## Common Use Cases\n\n### Geo-Location Testing\n\n```bash\n#!/bin/bash\n# Test site from different regions using geo-located proxies\n\nPROXIES=(\n    \"http://us-proxy.example.com:8080\"\n    \"http://eu-proxy.example.com:8080\"\n    \"http://asia-proxy.example.com:8080\"\n)\n\nfor proxy in \"${PROXIES[@]}\"; do\n    export HTTP_PROXY=\"$proxy\"\n    export HTTPS_PROXY=\"$proxy\"\n\n    region=$(echo \"$proxy\" | grep -oP '^\\w+-\\w+')\n    echo \"Testing from: $region\"\n\n    agent-browser --session \"$region\" open https://example.com\n    agent-browser --session \"$region\" screenshot \"./screenshots/$region.png\"\n    agent-browser --session \"$region\" close\ndone\n```\n\n### Rotating Proxies for Scraping\n\n```bash\n#!/bin/bash\n# Rotate through proxy list to avoid rate limiting\n\nPROXY_LIST=(\n    \"http://proxy1.example.com:8080\"\n    \"http://proxy2.example.com:8080\"\n    \"http://proxy3.example.com:8080\"\n)\n\nURLS=(\n    \"https://site.com/page1\"\n    \"https://site.com/page2\"\n    \"https://site.com/page3\"\n)\n\nfor i in \"${!URLS[@]}\"; do\n    proxy_index=$((i % ${#PROXY_LIST[@]}))\n    export HTTP_PROXY=\"${PROXY_LIST[$proxy_index]}\"\n    export HTTPS_PROXY=\"${PROXY_LIST[$proxy_index]}\"\n\n    agent-browser open \"${URLS[$i]}\"\n    agent-browser get text body > \"output-$i.txt\"\n    agent-browser close\n\n    sleep 1  # Polite delay\ndone\n```\n\n### Corporate Network Access\n\n```bash\n#!/bin/bash\n# Access internal sites via corporate proxy\n\nexport HTTP_PROXY=\"http://corpproxy.company.com:8080\"\nexport HTTPS_PROXY=\"http://corpproxy.company.com:8080\"\nexport NO_PROXY=\"localhost,127.0.0.1,.company.com\"\n\n# External sites go through proxy\nagent-browser open https://external-vendor.com\n\n# Internal sites bypass proxy\nagent-browser open https://intranet.company.com\n```\n\n## Verifying Proxy Connection\n\n```bash\n# Check your apparent IP\nagent-browser open https://httpbin.org/ip\nagent-browser get text body\n# Should show proxy's IP, not your real IP\n```\n\n## Troubleshooting\n\n### Proxy Connection Failed\n\n```bash\n# Test proxy connectivity first\ncurl -x http://proxy.example.com:8080 https://httpbin.org/ip\n\n# Check if proxy requires auth\nexport HTTP_PROXY=\"http://user:pass@proxy.example.com:8080\"\n```\n\n### SSL/TLS Errors Through Proxy\n\nSome proxies perform SSL inspection. If you encounter certificate errors:\n\n```bash\n# For testing only - not recommended for production\nagent-browser open https://example.com --ignore-https-errors\n```\n\n### Slow Performance\n\n```bash\n# Use proxy only when necessary\nexport NO_PROXY=\"*.cdn.com,*.static.com\"  # Direct CDN access\n```\n\n## Best Practices\n\n1. **Use environment variables** - Don't hardcode proxy credentials\n2. **Set NO_PROXY appropriately** - Avoid routing local traffic through proxy\n3. **Test proxy before automation** - Verify connectivity with simple requests\n4. **Handle proxy failures gracefully** - Implement retry logic for unstable proxies\n5. **Rotate proxies for large scraping jobs** - Distribute load and avoid bans\n","isInternal":false,"tokens":1055,"sizeBytes":4265},{"name":"session-management.md","path":".agents/skills/agent-browser/references/session-management.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/agent-browser/references/session-management.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Session Management\n\nRun multiple isolated browser sessions concurrently with state persistence.\n\n## Named Sessions\n\nUse `--session` flag to isolate browser contexts:\n\n```bash\n# Session 1: Authentication flow\nagent-browser --session auth open https://app.example.com/login\n\n# Session 2: Public browsing (separate cookies, storage)\nagent-browser --session public open https://example.com\n\n# Commands are isolated by session\nagent-browser --session auth fill @e1 \"user@example.com\"\nagent-browser --session public get text body\n```\n\n## Session Isolation Properties\n\nEach session has independent:\n- Cookies\n- LocalStorage / SessionStorage\n- IndexedDB\n- Cache\n- Browsing history\n- Open tabs\n\n## Session State Persistence\n\n### Save Session State\n\n```bash\n# Save cookies, storage, and auth state\nagent-browser state save /path/to/auth-state.json\n```\n\n### Load Session State\n\n```bash\n# Restore saved state\nagent-browser state load /path/to/auth-state.json\n\n# Continue with authenticated session\nagent-browser open https://app.example.com/dashboard\n```\n\n### State File Contents\n\n```json\n{\n  \"cookies\": [...],\n  \"localStorage\": {...},\n  \"sessionStorage\": {...},\n  \"origins\": [...]\n}\n```\n\n## Common Patterns\n\n### Authenticated Session Reuse\n\n```bash\n#!/bin/bash\n# Save login state once, reuse many times\n\nSTATE_FILE=\"/tmp/auth-state.json\"\n\n# Check if we have saved state\nif [[ -f \"$STATE_FILE\" ]]; then\n    agent-browser state load \"$STATE_FILE\"\n    agent-browser open https://app.example.com/dashboard\nelse\n    # Perform login\n    agent-browser open https://app.example.com/login\n    agent-browser snapshot -i\n    agent-browser fill @e1 \"$USERNAME\"\n    agent-browser fill @e2 \"$PASSWORD\"\n    agent-browser click @e3\n    agent-browser wait --load networkidle\n\n    # Save for future use\n    agent-browser state save \"$STATE_FILE\"\nfi\n```\n\n### Concurrent Scraping\n\n```bash\n#!/bin/bash\n# Scrape multiple sites concurrently\n\n# Start all sessions\nagent-browser --session site1 open https://site1.com &\nagent-browser --session site2 open https://site2.com &\nagent-browser --session site3 open https://site3.com &\nwait\n\n# Extract from each\nagent-browser --session site1 get text body > site1.txt\nagent-browser --session site2 get text body > site2.txt\nagent-browser --session site3 get text body > site3.txt\n\n# Cleanup\nagent-browser --session site1 close\nagent-browser --session site2 close\nagent-browser --session site3 close\n```\n\n### A/B Testing Sessions\n\n```bash\n# Test different user experiences\nagent-browser --session variant-a open \"https://app.com?variant=a\"\nagent-browser --session variant-b open \"https://app.com?variant=b\"\n\n# Compare\nagent-browser --session variant-a screenshot /tmp/variant-a.png\nagent-browser --session variant-b screenshot /tmp/variant-b.png\n```\n\n## Default Session\n\nWhen `--session` is omitted, commands use the default session:\n\n```bash\n# These use the same default session\nagent-browser open https://example.com\nagent-browser snapshot -i\nagent-browser close  # Closes default session\n```\n\n## Session Cleanup\n\n```bash\n# Close specific session\nagent-browser --session auth close\n\n# List active sessions\nagent-browser session list\n```\n\n## Best Practices\n\n### 1. Name Sessions Semantically\n\n```bash\n# GOOD: Clear purpose\nagent-browser --session github-auth open https://github.com\nagent-browser --session docs-scrape open https://docs.example.com\n\n# AVOID: Generic names\nagent-browser --session s1 open https://github.com\n```\n\n### 2. Always Clean Up\n\n```bash\n# Close sessions when done\nagent-browser --session auth close\nagent-browser --session scrape close\n```\n\n### 3. Handle State Files Securely\n\n```bash\n# Don't commit state files (contain auth tokens!)\necho \"*.auth-state.json\" >> .gitignore\n\n# Delete after use\nrm /tmp/auth-state.json\n```\n\n### 4. Timeout Long Sessions\n\n```bash\n# Set timeout for automated scripts\ntimeout 60 agent-browser --session long-task get text body\n```\n","isInternal":false,"tokens":911,"sizeBytes":3894},{"name":"snapshot-refs.md","path":".agents/skills/agent-browser/references/snapshot-refs.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/agent-browser/references/snapshot-refs.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Snapshot + Refs Workflow\n\nThe core innovation of agent-browser: compact element references that reduce context usage dramatically for AI agents.\n\n## How It Works\n\n### The Problem\nTraditional browser automation sends full DOM to AI agents:\n```\nFull DOM/HTML sent → AI parses → Generates CSS selector → Executes action\n~3000-5000 tokens per interaction\n```\n\n### The Solution\nagent-browser uses compact snapshots with refs:\n```\nCompact snapshot → @refs assigned → Direct ref interaction\n~200-400 tokens per interaction\n```\n\n## The Snapshot Command\n\n```bash\n# Basic snapshot (shows page structure)\nagent-browser snapshot\n\n# Interactive snapshot (-i flag) - RECOMMENDED\nagent-browser snapshot -i\n```\n\n### Snapshot Output Format\n\n```\nPage: Example Site - Home\nURL: https://example.com\n\n@e1 [header]\n  @e2 [nav]\n    @e3 [a] \"Home\"\n    @e4 [a] \"Products\"\n    @e5 [a] \"About\"\n  @e6 [button] \"Sign In\"\n\n@e7 [main]\n  @e8 [h1] \"Welcome\"\n  @e9 [form]\n    @e10 [input type=\"email\"] placeholder=\"Email\"\n    @e11 [input type=\"password\"] placeholder=\"Password\"\n    @e12 [button type=\"submit\"] \"Log In\"\n\n@e13 [footer]\n  @e14 [a] \"Privacy Policy\"\n```\n\n## Using Refs\n\nOnce you have refs, interact directly:\n\n```bash\n# Click the \"Sign In\" button\nagent-browser click @e6\n\n# Fill email input\nagent-browser fill @e10 \"user@example.com\"\n\n# Fill password\nagent-browser fill @e11 \"password123\"\n\n# Submit the form\nagent-browser click @e12\n```\n\n## Ref Lifecycle\n\n**IMPORTANT**: Refs are invalidated when the page changes!\n\n```bash\n# Get initial snapshot\nagent-browser snapshot -i\n# @e1 [button] \"Next\"\n\n# Click triggers page change\nagent-browser click @e1\n\n# MUST re-snapshot to get new refs!\nagent-browser snapshot -i\n# @e1 [h1] \"Page 2\"  ← Different element now!\n```\n\n## Best Practices\n\n### 1. Always Snapshot Before Interacting\n\n```bash\n# CORRECT\nagent-browser open https://example.com\nagent-browser snapshot -i          # Get refs first\nagent-browser click @e1            # Use ref\n\n# WRONG\nagent-browser open https://example.com\nagent-browser click @e1            # Ref doesn't exist yet!\n```\n\n### 2. Re-Snapshot After Navigation\n\n```bash\nagent-browser click @e5            # Navigates to new page\nagent-browser snapshot -i          # Get new refs\nagent-browser click @e1            # Use new refs\n```\n\n### 3. Re-Snapshot After Dynamic Changes\n\n```bash\nagent-browser click @e1            # Opens dropdown\nagent-browser snapshot -i          # See dropdown items\nagent-browser click @e7            # Select item\n```\n\n### 4. Snapshot Specific Regions\n\nFor complex pages, snapshot specific areas:\n\n```bash\n# Snapshot just the form\nagent-browser snapshot @e9\n```\n\n## Ref Notation Details\n\n```\n@e1 [tag type=\"value\"] \"text content\" placeholder=\"hint\"\n│    │   │             │               │\n│    │   │             │               └─ Additional attributes\n│    │   │             └─ Visible text\n│    │   └─ Key attributes shown\n│    └─ HTML tag name\n└─ Unique ref ID\n```\n\n### Common Patterns\n\n```\n@e1 [button] \"Submit\"                    # Button with text\n@e2 [input type=\"email\"]                 # Email input\n@e3 [input type=\"password\"]              # Password input\n@e4 [a href=\"/page\"] \"Link Text\"         # Anchor link\n@e5 [select]                             # Dropdown\n@e6 [textarea] placeholder=\"Message\"     # Text area\n@e7 [div class=\"modal\"]                  # Container (when relevant)\n@e8 [img alt=\"Logo\"]                     # Image\n@e9 [checkbox] checked                   # Checked checkbox\n@e10 [radio] selected                    # Selected radio\n```\n\n## Troubleshooting\n\n### \"Ref not found\" Error\n\n```bash\n# Ref may have changed - re-snapshot\nagent-browser snapshot -i\n```\n\n### Element Not Visible in Snapshot\n\n```bash\n# Scroll to reveal element\nagent-browser scroll --bottom\nagent-browser snapshot -i\n\n# Or wait for dynamic content\nagent-browser wait 1000\nagent-browser snapshot -i\n```\n\n### Too Many Elements\n\n```bash\n# Snapshot specific container\nagent-browser snapshot @e5\n\n# Or use get text for content-only extraction\nagent-browser get text @e5\n```\n","isInternal":false,"tokens":1051,"sizeBytes":4108},{"name":"video-recording.md","path":".agents/skills/agent-browser/references/video-recording.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/agent-browser/references/video-recording.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Recording Demos\n\nProduce polished, shareable demo videos with a visible cursor and minimal dead time.\n\nThis guide builds on the basics in `video-recording.md` with techniques specific to recording demos that look good to viewers.\n\n## Key Principles\n\n- **Minimize dead time** — chain commands with `&&` so tool-call overhead stays out of the video.\n- **Visible cursor** — headless Playwright has no cursor; inject a fake one via DOM.\n- **One chain per page** — navigation destroys the DOM cursor, so re-inject after every page change.\n- **No `sleep` calls** — tool execution provides enough natural pacing.\n\n## Step-by-step\n\n### 1. Set up the page BEFORE recording\n\nNavigate and snapshot **before** `record start`. Any time after recording starts is in the video.\n\n```bash\nagent-browser open <url>\n```\n```bash\nagent-browser snapshot -i\n```\n\nReview the snapshot to identify element refs (`@e1`, `@e2`, …) and plan the interaction sequence.\n\n### 2. Find element coordinates for mouse moves\n\nUse `getBoundingClientRect()` to get the (x, y) center of each target element. Do this **before** recording starts so the lookup doesn't appear in the video.\n\n```bash\nagent-browser eval \"(() => {\n  const el = document.querySelector('<selector>');\n  const r = el.getBoundingClientRect();\n  return JSON.stringify({ x: Math.round(r.x + r.width/2), y: Math.round(r.y + r.height/2) });\n})()\"\n```\n\n### 3. Inject a visible cursor\n\nHeadless Playwright doesn't render a cursor. Inject a DOM element that tracks mouse events:\n\n```bash\nagent-browser eval \"(() => {\n  const c = document.createElement('div');\n  c.style.cssText = 'width:20px;height:20px;border-radius:50%;background:rgba(255,50,50,0.85);position:fixed;top:290px;left:390px;z-index:999999;pointer-events:none;transition:top 0.15s ease,left 0.15s ease;box-shadow:0 0 8px rgba(255,50,50,0.5);';\n  document.body.appendChild(c);\n  document.addEventListener('mousemove', e => {\n    c.style.top=(e.clientY-10)+'px';\n    c.style.left=(e.clientX-10)+'px';\n  });\n})(); 'ok'\"\n```\n\n**Re-inject after every navigation** — page transitions destroy the DOM element.\n\n### 4. Chain all commands with `&&`\n\nEvery separate tool call adds seconds of dead time. Chain everything into as few bash calls as possible:\n\n```bash\nagent-browser record start ./demo.webm && agent-browser eval \"<inject cursor>\" && agent-browser mouse move 400 300 && agent-browser click @e1 && agent-browser mouse move 600 200 && agent-browser click @e2 && agent-browser record stop\n```\n\n### 5. Use `mouse move` before every click\n\nMove the cursor to the target first so viewers can follow the action:\n\n```bash\nagent-browser mouse move 623 52 && agent-browser click @e5\n```\n\n### 6. No `sleep` calls\n\nEvery millisecond counts. Tool execution itself provides enough natural pacing. Drop all `sleep` and `wait` calls entirely.\n\n### 7. One chain per page\n\nSince the cursor needs re-injection after navigation, structure as one `&&` chain per page. The recording persists across chains — only stop it in the final chain.\n\n```bash\n# Chain 1: first page → click navigates away\nagent-browser record start ./demo.webm && agent-browser eval \"<inject cursor>\" && agent-browser mouse move 400 300 && agent-browser click @e5\n```\n\n```bash\n# Chain 2: new page → finish\nagent-browser eval \"<inject cursor>\" && agent-browser mouse move 200 150 && agent-browser click @e3 && agent-browser scroll down 200 && agent-browser record stop\n```\n\n### 8. Serve the result\n\nCopy the `.webm` into `public/`, create a minimal viewer page, and share the sandbox URL.\n\n```bash\nmkdir -p public && cp demo.webm public/demo.webm\n```\n\nViewer page (`public/demo.html`):\n\n```html\n<!DOCTYPE html>\n<html>\n<head><title>Demo</title></head>\n<body style=\"margin:0;display:flex;justify-content:center;align-items:center;min-height:100vh;background:#111\">\n  <video controls autoplay style=\"max-width:90vw;max-height:90vh;border-radius:8px;box-shadow:0 4px 24px rgba(0,0,0,0.5)\" src=\"/demo.webm\"></video>\n</body>\n</html>\n```\n\n## Common Mistakes\n\n- **Recording before the page is ready** — navigate and snapshot first.\n- **Forgetting to re-inject the cursor after navigation** — the DOM element is gone.\n- **Separate tool calls instead of `&&` chains** — adds seconds of blank screen.\n- **Using `sleep` or `wait`** — unnecessary; tool execution provides natural pacing.\n- **Clicking without `mouse move`** — clicks without visible cursor movement look jarring.\n","isInternal":false,"tokens":1112,"sizeBytes":4439},{"name":"SKILL.md","path":".agents/skills/agent-browser/SKILL.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/agent-browser/SKILL.md","title":"agent-browser","category":"anthropic-skill","format":"markdown","content":"---\nname: agent-browser\ndescription: Automates browser interactions for web testing, form filling, screenshots, and data extraction. Use when the user needs to navigate websites, interact with web pages, fill forms, take screenshots, test web applications, or extract information from web pages.\nallowed-tools: Bash(agent-browser:*)\n---\n\n# Browser Automation with agent-browser\n\n## Quick start\n\n```bash\nagent-browser open <url>        # Navigate to page\nagent-browser snapshot -i       # Get interactive elements with refs\nagent-browser click @e1         # Click element by ref\nagent-browser fill @e2 \"text\"   # Fill input by ref\nagent-browser close             # Close browser\n```\n\n## Core workflow\n\n1. Navigate: `agent-browser open <url>`\n2. Snapshot: `agent-browser snapshot -i` (returns elements with refs like `@e1`, `@e2`)\n3. Interact using refs from the snapshot\n4. Re-snapshot after navigation or significant DOM changes\n\n## Commands\n\n### Navigation\n\n```bash\nagent-browser open <url>      # Navigate to URL (aliases: goto, navigate)\n                              # Supports: https://, http://, file://, about:, data://\n                              # Auto-prepends https:// if no protocol given\nagent-browser back            # Go back\nagent-browser forward         # Go forward\nagent-browser reload          # Reload page\nagent-browser close           # Close browser (aliases: quit, exit)\nagent-browser connect 9222    # Connect to browser via CDP port\n```\n\n### Snapshot (page analysis)\n\n```bash\nagent-browser snapshot            # Full accessibility tree\nagent-browser snapshot -i         # Interactive elements only (recommended)\nagent-browser snapshot -c         # Compact output\nagent-browser snapshot -d 3       # Limit depth to 3\nagent-browser snapshot -s \"#main\" # Scope to CSS selector\n```\n\n### Interactions (use @refs from snapshot)\n\n```bash\nagent-browser click @e1           # Click\nagent-browser dblclick @e1        # Double-click\nagent-browser focus @e1           # Focus element\nagent-browser fill @e2 \"text\"     # Clear and type\nagent-browser type @e2 \"text\"     # Type without clearing\nagent-browser press Enter         # Press key (alias: key)\nagent-browser press Control+a     # Key combination\nagent-browser keydown Shift       # Hold key down\nagent-browser keyup Shift         # Release key\nagent-browser hover @e1           # Hover\nagent-browser check @e1           # Check checkbox\nagent-browser uncheck @e1         # Uncheck checkbox\nagent-browser select @e1 \"value\"  # Select dropdown option\nagent-browser select @e1 \"a\" \"b\"  # Select multiple options\nagent-browser scroll down 500     # Scroll page (default: down 300px)\nagent-browser scrollintoview @e1  # Scroll element into view (alias: scrollinto)\nagent-browser drag @e1 @e2        # Drag and drop\nagent-browser upload @e1 file.pdf # Upload files\n```\n\n### Get information\n\n```bash\nagent-browser get text @e1        # Get element text\nagent-browser get html @e1        # Get innerHTML\nagent-browser get value @e1       # Get input value\nagent-browser get attr @e1 href   # Get attribute\nagent-browser get title           # Get page title\nagent-browser get url             # Get current URL\nagent-browser get count \".item\"   # Count matching elements\nagent-browser get box @e1         # Get bounding box\nagent-browser get styles @e1      # Get computed styles (font, color, bg, etc.)\n```\n\n### Check state\n\n```bash\nagent-browser is visible @e1      # Check if visible\nagent-browser is enabled @e1      # Check if enabled\nagent-browser is checked @e1      # Check if checked\n```\n\n### Screenshots & PDF\n\n```bash\nagent-browser screenshot          # Save to a temporary directory\nagent-browser screenshot path.png # Save to a specific path\nagent-browser screenshot --full   # Full page\nagent-browser pdf output.pdf      # Save as PDF\n```\n\n### Video recording\n\n```bash\nagent-browser record start ./demo.webm    # Start recording (uses current URL + state)\nagent-browser click @e1                   # Perform actions\nagent-browser record stop                 # Stop and save video\nagent-browser record restart ./take2.webm # Stop current + start new recording\n```\n\nRecording creates a fresh context but preserves cookies/storage from your session. If no URL is provided, it\nautomatically returns to your current page. For smooth demos, explore first, then start recording.\n\n### Wait\n\n```bash\nagent-browser wait @e1                     # Wait for element\nagent-browser wait 2000                    # Wait milliseconds\nagent-browser wait --text \"Success\"        # Wait for text (or -t)\nagent-browser wait --url \"**/dashboard\"    # Wait for URL pattern (or -u)\nagent-browser wait --load networkidle      # Wait for network idle (or -l)\nagent-browser wait --fn \"window.ready\"     # Wait for JS condition (or -f)\n```\n\n### Mouse control\n\n```bash\nagent-browser mouse move 100 200      # Move mouse\nagent-browser mouse down left         # Press button\nagent-browser mouse up left           # Release button\nagent-browser mouse wheel 100         # Scroll wheel\n```\n\n### Semantic locators (alternative to refs)\n\n```bash\nagent-browser find role button click --name \"Submit\"\nagent-browser find text \"Sign In\" click\nagent-browser find text \"Sign In\" click --exact      # Exact match only\nagent-browser find label \"Email\" fill \"user@test.com\"\nagent-browser find placeholder \"Search\" type \"query\"\nagent-browser find alt \"Logo\" click\nagent-browser find title \"Close\" click\nagent-browser find testid \"submit-btn\" click\nagent-browser find first \".item\" click\nagent-browser find last \".item\" click\nagent-browser find nth 2 \"a\" hover\n```\n\n### Browser settings\n\n```bash\nagent-browser set viewport 1920 1080          # Set viewport size\nagent-browser set device \"iPhone 14\"          # Emulate device\nagent-browser set geo 37.7749 -122.4194       # Set geolocation (alias: geolocation)\nagent-browser set offline on                  # Toggle offline mode\nagent-browser set headers '{\"X-Key\":\"v\"}'     # Extra HTTP headers\nagent-browser set credentials user pass       # HTTP basic auth (alias: auth)\nagent-browser set media dark                  # Emulate color scheme\nagent-browser set media light reduced-motion  # Light mode + reduced motion\n```\n\n### Cookies & Storage\n\n```bash\nagent-browser cookies                     # Get all cookies\nagent-browser cookies set name value      # Set cookie\nagent-browser cookies clear               # Clear cookies\nagent-browser storage local               # Get all localStorage\nagent-browser storage local key           # Get specific key\nagent-browser storage local set k v       # Set value\nagent-browser storage local clear         # Clear all\n```\n\n### Network\n\n```bash\nagent-browser network route <url>              # Intercept requests\nagent-browser network route <url> --abort      # Block requests\nagent-browser network route <url> --body '{}'  # Mock response\nagent-browser network unroute [url]            # Remove routes\nagent-browser network requests                 # View tracked requests\nagent-browser network requests --filter api    # Filter requests\n```\n\n### Tabs & Windows\n\n```bash\nagent-browser tab                 # List tabs\nagent-browser tab new [url]       # New tab\nagent-browser tab 2               # Switch to tab by index\nagent-browser tab close           # Close current tab\nagent-browser tab close 2         # Close tab by index\nagent-browser window new          # New window\n```\n\n### Frames\n\n```bash\nagent-browser frame \"#iframe\"     # Switch to iframe\nagent-browser frame main          # Back to main frame\n```\n\n### Dialogs\n\n```bash\nagent-browser dialog accept [text]  # Accept dialog\nagent-browser dialog dismiss        # Dismiss dialog\n```\n\n### JavaScript\n\n```bash\nagent-browser eval \"document.title\"   # Run JavaScript\n```\n\n## Global options\n\n```bash\nagent-browser --session <name> ...    # Isolated browser session\nagent-browser --json ...              # JSON output for parsing\nagent-browser --headed ...            # Show browser window (not headless)\nagent-browser --full ...              # Full page screenshot (-f)\nagent-browser --cdp <port> ...        # Connect via Chrome DevTools Protocol\nagent-browser -p <provider> ...       # Cloud browser provider (--provider)\nagent-browser --proxy <url> ...       # Use proxy server\nagent-browser --headers <json> ...    # HTTP headers scoped to URL's origin\nagent-browser --executable-path <p>   # Custom browser executable\nagent-browser --extension <path> ...  # Load browser extension (repeatable)\nagent-browser --help                  # Show help (-h)\nagent-browser --version               # Show version (-V)\nagent-browser <command> --help        # Show detailed help for a command\n```\n\n### Proxy support\n\n```bash\nagent-browser --proxy http://proxy.com:8080 open example.com\nagent-browser --proxy http://user:pass@proxy.com:8080 open example.com\nagent-browser --proxy socks5://proxy.com:1080 open example.com\n```\n\n## Environment variables\n\n```bash\nAGENT_BROWSER_SESSION=\"mysession\"            # Default session name\nAGENT_BROWSER_EXECUTABLE_PATH=\"/path/chrome\" # Custom browser path\nAGENT_BROWSER_EXTENSIONS=\"/ext1,/ext2\"       # Comma-separated extension paths\nAGENT_BROWSER_PROVIDER=\"your-cloud-browser-provider\"  # Cloud browser provider (select browseruse or browserbase)\nAGENT_BROWSER_STREAM_PORT=\"9223\"             # WebSocket streaming port\nAGENT_BROWSER_HOME=\"/path/to/agent-browser\"  # Custom install location (for daemon.js)\n```\n\n## Example: Form submission\n\n```bash\nagent-browser open https://example.com/form\nagent-browser snapshot -i\n# Output shows: textbox \"Email\" [ref=e1], textbox \"Password\" [ref=e2], button \"Submit\" [ref=e3]\n\nagent-browser fill @e1 \"user@example.com\"\nagent-browser fill @e2 \"password123\"\nagent-browser click @e3\nagent-browser wait --load networkidle\nagent-browser snapshot -i  # Check result\n```\n\n## Example: Authentication with saved state\n\n```bash\n# Login once\nagent-browser open https://app.example.com/login\nagent-browser snapshot -i\nagent-browser fill @e1 \"username\"\nagent-browser fill @e2 \"password\"\nagent-browser click @e3\nagent-browser wait --url \"**/dashboard\"\nagent-browser state save auth.json\n\n# Later sessions: load saved state\nagent-browser state load auth.json\nagent-browser open https://app.example.com/dashboard\n```\n\n## Sessions (parallel browsers)\n\n```bash\nagent-browser --session test1 open site-a.com\nagent-browser --session test2 open site-b.com\nagent-browser session list\n```\n\n## JSON output (for parsing)\n\nAdd `--json` for machine-readable output:\n\n```bash\nagent-browser snapshot -i --json\nagent-browser get text @e1 --json\n```\n\n## Debugging\n\n```bash\nagent-browser --headed open example.com   # Show browser window\nagent-browser --cdp 9222 snapshot         # Connect via CDP port\nagent-browser connect 9222                # Alternative: connect command\nagent-browser console                     # View console messages\nagent-browser console --clear             # Clear console\nagent-browser errors                      # View page errors\nagent-browser errors --clear              # Clear errors\nagent-browser highlight @e1               # Highlight element\nagent-browser trace start                 # Start recording trace\nagent-browser trace stop trace.zip        # Stop and save trace\nagent-browser record start ./debug.webm   # Record video from current page\nagent-browser record stop                 # Save recording\n```\n\n## Deep-dive documentation\n\nFor detailed patterns and best practices, see:\n\n| Reference | Description |\n|-----------|-------------|\n| [references/snapshot-refs.md](references/snapshot-refs.md) | Ref lifecycle, invalidation rules, troubleshooting |\n| [references/session-management.md](references/session-management.md) | Parallel sessions, state persistence, concurrent scraping |\n| [references/authentication.md](references/authentication.md) | Login flows, OAuth, 2FA handling, state reuse |\n| [references/video-recording.md](references/video-recording.md) | Recording workflows for debugging and documentation |\n| [references/proxy-support.md](references/proxy-support.md) | Proxy configuration, geo-testing, rotating proxies |\n\n## Ready-to-use templates\n\nExecutable workflow scripts for common patterns:\n\n| Template | Description |\n|----------|-------------|\n| [templates/form-automation.sh](templates/form-automation.sh) | Form filling with validation |\n| [templates/authenticated-session.sh](templates/authenticated-session.sh) | Login once, reuse state |\n| [templates/capture-workflow.sh](templates/capture-workflow.sh) | Content extraction with screenshots |\n\nUsage:\n```bash\n./templates/form-automation.sh https://example.com/form\n./templates/authenticated-session.sh https://app.example.com/login\n./templates/capture-workflow.sh https://example.com ./output\n```\n\n## HTTPS Certificate Errors\n\nFor sites with self-signed or invalid certificates:\n```bash\nagent-browser open https://localhost:8443 --ignore-https-errors\n```\n","frontmatter":{"name":"agent-browser","description":"Automates browser interactions for web testing, form filling, screenshots, and data extraction. Use when the user needs to navigate websites, interact with web pages, fill forms, take screenshots, test web applications, or extract information from web pages.","allowed-tools":"Bash(agent-browser:*)","allowedTools":["Bash(agent-browser:*)"]},"allowedTools":["Bash(agent-browser:*)"],"isInternal":false,"tokens":2847,"sizeBytes":12895},{"name":"ai-gateway.md","path":".agents/skills/ai-sdk/references/ai-gateway.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/ai-sdk/references/ai-gateway.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Vercel AI Gateway\ndescription: Reference for using Vercel AI Gateway with the AI SDK.\n---\n\n# Vercel AI Gateway\n\nThe Vercel AI Gateway is the fastest way to get started with the AI SDK. It provides access to models from OpenAI, Anthropic, Google, and other providers through a single API.\n\n## Authentication\n\nAuthenticate with OIDC (for Vercel deployments) or an [AI Gateway API key](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai-gateway%2Fapi-keys&title=AI+Gateway+API+Keys):\n\n```env filename=\".env.local\"\nAI_GATEWAY_API_KEY=your_api_key_here\n```\n\n## Usage\n\nThe AI Gateway is the default global provider, so you can access models using a simple string:\n\n```ts\nimport { generateText } from 'ai';\n\nconst { text } = await generateText({\n  model: 'anthropic/claude-sonnet-4.5',\n  prompt: 'What is love?',\n});\n```\n\nYou can also explicitly import and use the gateway provider:\n\n```ts\n// Option 1: Import from 'ai' package (included by default)\nimport { gateway } from 'ai';\nmodel: gateway('anthropic/claude-sonnet-4.5');\n\n// Option 2: Install and import from '@ai-sdk/gateway' package\nimport { gateway } from '@ai-sdk/gateway';\nmodel: gateway('anthropic/claude-sonnet-4.5');\n```\n\n## Find Available Models\n\n**Important**: Always fetch the current model list before writing code. Never use model IDs from memory - they may be outdated.\n\nList all available models through the gateway API:\n\n```bash\ncurl https://ai-gateway.vercel.sh/v1/models\n```\n\nFilter by provider using `jq`. **Do not truncate with `head`** - always fetch the full list to find the latest models:\n\n```bash\n# Anthropic models\ncurl -s https://ai-gateway.vercel.sh/v1/models | jq -r '[.data[] | select(.id | startswith(\"anthropic/\")) | .id] | reverse | .[]'\n\n# OpenAI models\ncurl -s https://ai-gateway.vercel.sh/v1/models | jq -r '[.data[] | select(.id | startswith(\"openai/\")) | .id] | reverse | .[]'\n\n# Google models\ncurl -s https://ai-gateway.vercel.sh/v1/models | jq -r '[.data[] | select(.id | startswith(\"google/\")) | .id] | reverse | .[]'\n```\n\nWhen multiple versions of a model exist, use the one with the highest version number (e.g., prefer `claude-sonnet-4-5` over `claude-sonnet-4` over `claude-3-5-sonnet`).\n","frontmatter":{"title":"Vercel AI Gateway","description":"Reference for using Vercel AI Gateway with the AI SDK."},"isInternal":false,"tokens":623,"sizeBytes":2191},{"name":"common-errors.md","path":".agents/skills/ai-sdk/references/common-errors.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/ai-sdk/references/common-errors.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Common Errors\ndescription: Reference for common AI SDK errors and how to resolve them.\n---\n\n# Common Errors\n\n## `maxTokens` → `maxOutputTokens`\n\n```typescript\n// ❌ Incorrect\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  maxTokens: 512, // deprecated: use `maxOutputTokens` instead\n  prompt: 'Write a short story',\n});\n\n// ✅ Correct\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  maxOutputTokens: 512,\n  prompt: 'Write a short story',\n});\n```\n\n## `maxSteps` → `stopWhen: stepCountIs(n)`\n\n```typescript\n// ❌ Incorrect\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  tools: { weather },\n  maxSteps: 5, // deprecated: use `stopWhen: stepCountIs(n)` instead\n  prompt: 'What is the weather in NYC?',\n});\n\n// ✅ Correct\nimport { generateText, stepCountIs } from 'ai';\n\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  tools: { weather },\n  stopWhen: stepCountIs(5),\n  prompt: 'What is the weather in NYC?',\n});\n```\n\n## `parameters` → `inputSchema` (in tool definition)\n\n```typescript\n// ❌ Incorrect\nconst weatherTool = tool({\n  description: 'Get weather for a location',\n  parameters: z.object({\n    // deprecated: use `inputSchema` instead\n    location: z.string(),\n  }),\n  execute: async ({ location }) => ({ location, temp: 72 }),\n});\n\n// ✅ Correct\nconst weatherTool = tool({\n  description: 'Get weather for a location',\n  inputSchema: z.object({\n    location: z.string(),\n  }),\n  execute: async ({ location }) => ({ location, temp: 72 }),\n});\n```\n\n## `generateObject` → `generateText` with `output`\n\n`generateObject` is deprecated. Use `generateText` with the `output` option instead.\n\n```typescript\n// ❌ Deprecated\nimport { generateObject } from 'ai'; // deprecated: use `generateText` with `output` instead\n\nconst result = await generateObject({\n  // deprecated function\n  model: 'anthropic/claude-opus-4.5',\n  schema: z.object({\n    // deprecated: use `Output.object({ schema })` instead\n    recipe: z.object({\n      name: z.string(),\n      ingredients: z.array(z.string()),\n    }),\n  }),\n  prompt: 'Generate a recipe for chocolate cake',\n});\n\n// ✅ Correct\nimport { generateText, Output } from 'ai';\n\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  output: Output.object({\n    schema: z.object({\n      recipe: z.object({\n        name: z.string(),\n        ingredients: z.array(z.string()),\n      }),\n    }),\n  }),\n  prompt: 'Generate a recipe for chocolate cake',\n});\n\nconsole.log(result.output); // typed object\n```\n\n## Manual JSON parsing → `generateText` with `output`\n\n```typescript\n// ❌ Incorrect\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  prompt: `Extract the user info as JSON: { \"name\": string, \"age\": number }\n\n  Input: John is 25 years old`,\n});\nconst parsed = JSON.parse(result.text);\n\n// ✅ Correct\nimport { generateText, Output } from 'ai';\n\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  output: Output.object({\n    schema: z.object({\n      name: z.string(),\n      age: z.number(),\n    }),\n  }),\n  prompt: 'Extract the user info: John is 25 years old',\n});\n\nconsole.log(result.output); // { name: 'John', age: 25 }\n```\n\n## Other `output` options\n\n```typescript\n// Output.array - for generating arrays of items\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  output: Output.array({\n    element: z.object({\n      city: z.string(),\n      country: z.string(),\n    }),\n  }),\n  prompt: 'List 5 capital cities',\n});\n\n// Output.choice - for selecting from predefined options\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  output: Output.choice({\n    options: ['positive', 'negative', 'neutral'] as const,\n  }),\n  prompt: 'Classify the sentiment: I love this product!',\n});\n\n// Output.json - for untyped JSON output\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  output: Output.json(),\n  prompt: 'Return some JSON data',\n});\n```\n\n## `toDataStreamResponse` → `toUIMessageStreamResponse`\n\nWhen using `useChat` on the frontend, use `toUIMessageStreamResponse()` instead of `toDataStreamResponse()`. The UI message stream format is designed to work with the chat UI components and handles message state correctly.\n\n```typescript\n// ❌ Incorrect (when using useChat)\nconst result = streamText({\n  // config\n});\n\nreturn result.toDataStreamResponse(); // deprecated for useChat: use toUIMessageStreamResponse\n\n// ✅ Correct\nconst result = streamText({\n  // config\n});\n\nreturn result.toUIMessageStreamResponse();\n```\n\n## Removed managed input state in `useChat`\n\nThe `useChat` hook no longer manages input state internally. You must now manage input state manually.\n\n```tsx\n// ❌ Deprecated\nimport { useChat } from '@ai-sdk/react';\n\nexport default function Page() {\n  const {\n    input, // deprecated: manage input state manually with useState\n    handleInputChange, // deprecated: use custom onChange handler\n    handleSubmit, // deprecated: use sendMessage() instead\n  } = useChat({\n    api: '/api/chat', // deprecated: use `transport: new DefaultChatTransport({ api })` instead\n  });\n\n  return (\n    <form onSubmit={handleSubmit}>\n      <input value={input} onChange={handleInputChange} />\n      <button type=\"submit\">Send</button>\n    </form>\n  );\n}\n\n// ✅ Correct\nimport { useChat } from '@ai-sdk/react';\nimport { DefaultChatTransport } from 'ai';\nimport { useState } from 'react';\n\nexport default function Page() {\n  const [input, setInput] = useState('');\n  const { sendMessage } = useChat({\n    transport: new DefaultChatTransport({ api: '/api/chat' }),\n  });\n\n  const handleSubmit = e => {\n    e.preventDefault();\n    sendMessage({ text: input });\n    setInput('');\n  };\n\n  return (\n    <form onSubmit={handleSubmit}>\n      <input value={input} onChange={e => setInput(e.target.value)} />\n      <button type=\"submit\">Send</button>\n    </form>\n  );\n}\n```\n\n## `tool-invocation` → `tool-{toolName}` (typed tool parts)\n\nWhen rendering messages with `useChat`, use the typed tool part names (`tool-{toolName}`) instead of the generic `tool-invocation` type. This provides better type safety and access to tool-specific input/output types.\n\n> For end-to-end type-safety, see [Type-Safe Agents](type-safe-agents.md).\n\nTyped tool parts also use different property names:\n\n- `part.args` → `part.input`\n- `part.result` → `part.output`\n\n```tsx\n// ❌ Incorrect - using generic tool-invocation\n{\n  message.parts.map((part, i) => {\n    switch (part.type) {\n      case 'text':\n        return <div key={`${message.id}-${i}`}>{part.text}</div>;\n      case 'tool-invocation': // deprecated: use typed tool parts instead\n        return (\n          <pre key={`${message.id}-${i}`}>\n            {JSON.stringify(part.toolInvocation, null, 2)}\n          </pre>\n        );\n    }\n  });\n}\n\n// ✅ Correct - using typed tool parts (recommended)\n{\n  message.parts.map(part => {\n    switch (part.type) {\n      case 'text':\n        return part.text;\n      case 'tool-askForConfirmation':\n        // handle askForConfirmation tool\n        break;\n      case 'tool-getWeatherInformation':\n        // handle getWeatherInformation tool\n        break;\n    }\n  });\n}\n\n// ✅ Alternative - using isToolUIPart as a catch-all\nimport { isToolUIPart } from 'ai';\n\n{\n  message.parts.map(part => {\n    if (part.type === 'text') {\n      return part.text;\n    }\n    if (isToolUIPart(part)) {\n      // handle any tool part generically\n      return (\n        <div key={part.toolCallId}>\n          {part.toolName}: {part.state}\n        </div>\n      );\n    }\n  });\n}\n```\n\n## `useChat` state-dependent property access\n\nTool part properties are only available in certain states. TypeScript will error if you access them without checking state first.\n\n```tsx\n// ❌ Incorrect - input may be undefined during streaming\n// TS18048: 'part.input' is possibly 'undefined'\nif (part.type === 'tool-getWeather') {\n  const location = part.input.location;\n}\n\n// ✅ Correct - check for input-available or output-available\nif (\n  part.type === 'tool-getWeather' &&\n  (part.state === 'input-available' || part.state === 'output-available')\n) {\n  const location = part.input.location;\n}\n\n// ❌ Incorrect - output is only available after execution\n// TS18048: 'part.output' is possibly 'undefined'\nif (part.type === 'tool-getWeather') {\n  const weather = part.output;\n}\n\n// ✅ Correct - check for output-available\nif (part.type === 'tool-getWeather' && part.state === 'output-available') {\n  const location = part.input.location;\n  const weather = part.output;\n}\n```\n\n## `part.toolInvocation.args` → `part.input`\n\n```tsx\n// ❌ Incorrect\nif (part.type === 'tool-invocation') {\n  // deprecated: use `part.input` on typed tool parts instead\n  const location = part.toolInvocation.args.location;\n}\n\n// ✅ Correct\nif (\n  part.type === 'tool-getWeather' &&\n  (part.state === 'input-available' || part.state === 'output-available')\n) {\n  const location = part.input.location;\n}\n```\n\n## `part.toolInvocation.result` → `part.output`\n\n```tsx\n// ❌ Incorrect\nif (part.type === 'tool-invocation') {\n  // deprecated: use `part.output` on typed tool parts instead\n  const weather = part.toolInvocation.result;\n}\n\n// ✅ Correct\nif (part.type === 'tool-getWeather' && part.state === 'output-available') {\n  const weather = part.output;\n}\n```\n\n## `part.toolInvocation.toolCallId` → `part.toolCallId`\n\n```tsx\n// ❌ Incorrect\nif (part.type === 'tool-invocation') {\n  // deprecated: use `part.toolCallId` on typed tool parts instead\n  const id = part.toolInvocation.toolCallId;\n}\n\n// ✅ Correct\nif (part.type === 'tool-getWeather') {\n  const id = part.toolCallId;\n}\n```\n\n## Tool invocation states renamed\n\n```tsx\n// ❌ Incorrect\nswitch (part.toolInvocation.state) {\n  case 'partial-call': // deprecated: use `input-streaming` instead\n    return <div>Loading...</div>;\n  case 'call': // deprecated: use `input-available` instead\n    return <div>Executing...</div>;\n  case 'result': // deprecated: use `output-available` instead\n    return <div>Done</div>;\n}\n\n// ✅ Correct\nswitch (part.state) {\n  case 'input-streaming':\n    return <div>Loading...</div>;\n  case 'input-available':\n    return <div>Executing...</div>;\n  case 'output-available':\n    return <div>Done</div>;\n}\n```\n\n## `addToolResult` → `addToolOutput`\n\n```tsx\n// ❌ Incorrect\naddToolResult({\n  // deprecated: use `addToolOutput` instead\n  toolCallId: part.toolInvocation.toolCallId,\n  result: 'Yes, confirmed.', // deprecated: use `output` instead\n});\n\n// ✅ Correct\naddToolOutput({\n  tool: 'askForConfirmation',\n  toolCallId: part.toolCallId,\n  output: 'Yes, confirmed.',\n});\n```\n\n## `messages` → `uiMessages` in `createAgentUIStreamResponse`\n\n```typescript\n// ❌ Incorrect\nreturn createAgentUIStreamResponse({\n  agent: myAgent,\n  messages, // incorrect: use `uiMessages` instead\n});\n\n// ✅ Correct\nreturn createAgentUIStreamResponse({\n  agent: myAgent,\n  uiMessages: messages,\n});\n```\n","frontmatter":{"title":"Common Errors","description":"Reference for common AI SDK errors and how to resolve them."},"isInternal":false,"tokens":2784,"sizeBytes":11034},{"name":"devtools.md","path":".agents/skills/ai-sdk/references/devtools.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/ai-sdk/references/devtools.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: AI SDK DevTools\ndescription: Debug AI SDK calls by inspecting captured runs and steps.\n---\n\n# AI SDK DevTools\n\n## Why Use DevTools\n\nDevTools captures all AI SDK calls (`generateText`, `streamText`, `ToolLoopAgent`) to a local JSON file. This lets you inspect LLM requests, responses, tool calls, and multi-step interactions without manually logging.\n\n## Setup\n\nRequires AI SDK 6. Install `@ai-sdk/devtools` using your project's package manager.\n\nWrap your model with the middleware:\n\n```ts\nimport { wrapLanguageModel, gateway } from 'ai';\nimport { devToolsMiddleware } from '@ai-sdk/devtools';\n\nconst model = wrapLanguageModel({\n  model: gateway('anthropic/claude-sonnet-4.5'),\n  middleware: devToolsMiddleware(),\n});\n```\n\n## Viewing Captured Data\n\nAll runs and steps are saved to:\n\n```\n.devtools/generations.json\n```\n\nRead this file directly to inspect captured data:\n\n```bash\ncat .devtools/generations.json | jq\n```\n\nOr launch the web UI:\n\n```bash\nnpx @ai-sdk/devtools\n# Open http://localhost:4983\n```\n\n## Data Structure\n\n- **Run**: A complete multi-step interaction grouped by initial prompt\n- **Step**: A single LLM call within a run (includes input, output, tool calls, token usage)\n","frontmatter":{"title":"AI SDK DevTools","description":"Debug AI SDK calls by inspecting captured runs and steps."},"isInternal":false,"tokens":296,"sizeBytes":1199},{"name":"type-safe-agents.md","path":".agents/skills/ai-sdk/references/type-safe-agents.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/ai-sdk/references/type-safe-agents.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Type-Safe useChat with Agents\ndescription: Build end-to-end type-safe agents by inferring UIMessage types from your agent definition.\n---\n\n# Type-Safe useChat with Agents\n\nBuild end-to-end type-safe agents by inferring `UIMessage` types from your agent definition for type-safe UI rendering with `useChat`.\n\n## Recommended Structure\n\n```\nlib/\n  agents/\n    my-agent.ts       # Agent definition + type export\n  tools/\n    weather-tool.ts   # Individual tool definitions\n    calculator-tool.ts\n```\n\n## Define Tools\n\n```ts\n// lib/tools/weather-tool.ts\nimport { tool } from 'ai';\nimport { z } from 'zod';\n\nexport const weatherTool = tool({\n  description: 'Get current weather for a location',\n  inputSchema: z.object({\n    location: z.string().describe('City name'),\n  }),\n  execute: async ({ location }) => {\n    return { temperature: 72, condition: 'sunny', location };\n  },\n});\n```\n\n## Define Agent and Export Type\n\n```ts\n// lib/agents/my-agent.ts\nimport { ToolLoopAgent, InferAgentUIMessage } from 'ai';\nimport { weatherTool } from '../tools/weather-tool';\nimport { calculatorTool } from '../tools/calculator-tool';\n\nexport const myAgent = new ToolLoopAgent({\n  model: 'anthropic/claude-sonnet-4',\n  instructions: 'You are a helpful assistant.',\n  tools: {\n    weather: weatherTool,\n    calculator: calculatorTool,\n  },\n});\n\n// Infer the UIMessage type from the agent\nexport type MyAgentUIMessage = InferAgentUIMessage<typeof myAgent>;\n```\n\n### With Custom Metadata\n\n```ts\n// lib/agents/my-agent.ts\nimport { z } from 'zod';\n\nconst metadataSchema = z.object({\n  createdAt: z.number(),\n  model: z.string().optional(),\n});\n\ntype MyMetadata = z.infer<typeof metadataSchema>;\n\nexport type MyAgentUIMessage = InferAgentUIMessage<typeof myAgent, MyMetadata>;\n```\n\n## Use with `useChat`\n\n```tsx\n// app/chat.tsx\nimport { useChat } from '@ai-sdk/react';\nimport type { MyAgentUIMessage } from '@/lib/agents/my-agent';\n\nexport function Chat() {\n  const { messages } = useChat<MyAgentUIMessage>();\n\n  return (\n    <div>\n      {messages.map(message => (\n        <Message key={message.id} message={message} />\n      ))}\n    </div>\n  );\n}\n```\n\n## Rendering Parts with Type Safety\n\nTool parts are typed as `tool-{toolName}` based on your agent's tools:\n\n```tsx\nfunction Message({ message }: { message: MyAgentUIMessage }) {\n  return (\n    <div>\n      {message.parts.map((part, i) => {\n        switch (part.type) {\n          case 'text':\n            return <p key={i}>{part.text}</p>;\n\n          case 'tool-weather':\n            // part.input and part.output are fully typed\n            if (part.state === 'output-available') {\n              return (\n                <div key={i}>\n                  Weather in {part.input.location}: {part.output.temperature}F\n                </div>\n              );\n            }\n            return <div key={i}>Loading weather...</div>;\n\n          case 'tool-calculator':\n            // TypeScript knows this is the calculator tool\n            return <div key={i}>Calculating...</div>;\n\n          default:\n            return null;\n        }\n      })}\n    </div>\n  );\n}\n```\n\nThe `part.type` discriminant narrows the type, giving you autocomplete and type checking for `input` and `output` based on each tool's schema.\n\n## Splitting Tool Rendering into Components\n\nWhen rendering many tools, you may want to split each tool into its own component. Use `UIToolInvocation<TOOL>` to derive a typed invocation from your tool and export it alongside the tool definition:\n\n```ts\n// lib/tools/weather-tool.ts\nimport { tool, UIToolInvocation } from 'ai';\nimport { z } from 'zod';\n\nexport const weatherTool = tool({\n  description: 'Get current weather for a location',\n  inputSchema: z.object({\n    location: z.string().describe('City name'),\n  }),\n  execute: async ({ location }) => {\n    return { temperature: 72, condition: 'sunny', location };\n  },\n});\n\n// Export the invocation type for use in UI components\nexport type WeatherToolInvocation = UIToolInvocation<typeof weatherTool>;\n```\n\nThen import only the type in your component:\n\n```tsx\n// components/weather-tool.tsx\nimport type { WeatherToolInvocation } from '@/lib/tools/weather-tool';\n\nexport function WeatherToolComponent({\n  invocation,\n}: {\n  invocation: WeatherToolInvocation;\n}) {\n  // invocation.input and invocation.output are fully typed\n  if (invocation.state === 'output-available') {\n    return (\n      <div>\n        Weather in {invocation.input.location}: {invocation.output.temperature}F\n      </div>\n    );\n  }\n  return <div>Loading weather for {invocation.input?.location}...</div>;\n}\n```\n\nUse the component in your message renderer:\n\n```tsx\nfunction Message({ message }: { message: MyAgentUIMessage }) {\n  return (\n    <div>\n      {message.parts.map((part, i) => {\n        switch (part.type) {\n          case 'text':\n            return <p key={i}>{part.text}</p>;\n          case 'tool-weather':\n            return <WeatherToolComponent key={i} invocation={part} />;\n          case 'tool-calculator':\n            return <CalculatorToolComponent key={i} invocation={part} />;\n          default:\n            return null;\n        }\n      })}\n    </div>\n  );\n}\n```\n\nThis approach keeps your tool rendering logic organized while maintaining full type safety, without needing to import the tool implementation into your UI components.\n","frontmatter":{"title":"Type-Safe useChat with Agents","description":"Build end-to-end type-safe agents by inferring UIMessage types from your agent definition."},"isInternal":false,"tokens":1242,"sizeBytes":5323},{"name":"SKILL.md","path":".agents/skills/ai-sdk/SKILL.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/ai-sdk/SKILL.md","title":"ai-sdk","category":"anthropic-skill","format":"markdown","content":"---\nname: ai-sdk\ndescription: 'Answer questions about the AI SDK and help build AI-powered features. Use when developers: (1) Ask about AI SDK functions like generateText, streamText, ToolLoopAgent, embed, or tools, (2) Want to build AI agents, chatbots, RAG systems, or text generation features, (3) Have questions about AI providers (OpenAI, Anthropic, Google, etc.), streaming, tool calling, structured output, or embeddings, (4) Use React hooks like useChat or useCompletion. Triggers on: \"AI SDK\", \"Vercel AI SDK\", \"generateText\", \"streamText\", \"add AI to my app\", \"build an agent\", \"tool calling\", \"structured output\", \"useChat\".'\n---\n\n## Prerequisites\n\nBefore searching docs, check if `node_modules/ai/docs/` exists. If not, install **only** the `ai` package using the project's package manager (e.g., `pnpm add ai`).\n\nDo not install other packages at this stage. Provider packages (e.g., `@ai-sdk/openai`) and client packages (e.g., `@ai-sdk/react`) should be installed later when needed based on user requirements.\n\n## Critical: Do Not Trust Internal Knowledge\n\nEverything you know about the AI SDK is outdated or wrong. Your training data contains obsolete APIs, deprecated patterns, and incorrect usage.\n\n**When working with the AI SDK:**\n\n1. Ensure `ai` package is installed (see Prerequisites)\n2. Search `node_modules/ai/docs/` and `node_modules/ai/src/` for current APIs\n3. If not found locally, search ai-sdk.dev documentation (instructions below)\n4. Never rely on memory - always verify against source code or docs\n5. **`useChat` has changed significantly** - check [Common Errors](references/common-errors.md) before writing client code\n6. When deciding which model and provider to use (e.g. OpenAI, Anthropic, Gemini), use the Vercel AI Gateway provider unless the user specifies otherwise. See [AI Gateway Reference](references/ai-gateway.md) for usage details.\n7. **Always fetch current model IDs** - Never use model IDs from memory. Before writing code that uses a model, run `curl -s https://ai-gateway.vercel.sh/v1/models | jq -r '[.data[] | select(.id | startswith(\"provider/\")) | .id] | reverse | .[]'` (replacing `provider` with the relevant provider like `anthropic`, `openai`, or `google`) to get the full list with newest models first. Use the model with the highest version number (e.g., `claude-sonnet-4-5` over `claude-sonnet-4` over `claude-3-5-sonnet`).\n8. Run typecheck after changes to ensure code is correct\n9. **Be minimal** - Only specify options that differ from defaults. When unsure of defaults, check docs or source rather than guessing or over-specifying.\n\nIf you cannot find documentation to support your answer, state that explicitly.\n\n## Finding Documentation\n\n### ai@6.0.34+\n\nSearch bundled docs and source in `node_modules/ai/`:\n\n- **Docs**: `grep \"query\" node_modules/ai/docs/`\n- **Source**: `grep \"query\" node_modules/ai/src/`\n\nProvider packages include docs at `node_modules/@ai-sdk/<provider>/docs/`.\n\n### Earlier versions\n\n1. Search: `https://ai-sdk.dev/api/search-docs?q=your_query`\n2. Fetch `.md` URLs from results (e.g., `https://ai-sdk.dev/docs/agents/building-agents.md`)\n\n## When Typecheck Fails\n\n**Before searching source code**, grep [Common Errors](references/common-errors.md) for the failing property or function name. Many type errors are caused by deprecated APIs documented there.\n\nIf not found in common-errors.md:\n\n1. Search `node_modules/ai/src/` and `node_modules/ai/docs/`\n2. Search ai-sdk.dev (for earlier versions or if not found locally)\n\n## Building and Consuming Agents\n\n### Creating Agents\n\nAlways use the `ToolLoopAgent` pattern. Search `node_modules/ai/docs/` for current agent creation APIs.\n\n**File conventions**: See [type-safe-agents.md](references/type-safe-agents.md) for where to save agents and tools.\n\n**Type Safety**: When consuming agents with `useChat`, always use `InferAgentUIMessage<typeof agent>` for type-safe tool results. See [reference](references/type-safe-agents.md).\n\n### Consuming Agents (Framework-Specific)\n\nBefore implementing agent consumption:\n\n1. Check `package.json` to detect the project's framework/stack\n2. Search documentation for the framework's quickstart guide\n3. Follow the framework-specific patterns for streaming, API routes, and client integration\n\n## References\n\n- [Common Errors](references/common-errors.md) - Renamed parameters reference (parameters → inputSchema, etc.)\n- [AI Gateway](references/ai-gateway.md) - Gateway setup and usage\n- [Type-Safe Agents with useChat](references/type-safe-agents.md) - End-to-end type safety with InferAgentUIMessage\n- [DevTools](references/devtools.md) - Set up local debugging and observability (development only)\n","frontmatter":{"name":"ai-sdk","description":"Answer questions about the AI SDK and help build AI-powered features. Use when developers: (1) Ask about AI SDK functions like generateText, streamText, ToolLoopAgent, embed, or tools, (2) Want to build AI agents, chatbots, RAG systems, or text generation features, (3) Have questions about AI providers (OpenAI, Anthropic, Google, etc.), streaming, tool calling, structured output, or embeddings, (4) Use React hooks like useChat or useCompletion. Triggers on: \"AI SDK\", \"Vercel AI SDK\", \"generateText\", \"streamText\", \"add AI to my app\", \"build an agent\", \"tool calling\", \"structured output\", \"useChat\"."},"isInternal":false,"tokens":1133,"sizeBytes":4683},{"name":"SKILL.md","path":".agents/skills/baseline-ui/SKILL.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/baseline-ui/SKILL.md","title":"baseline-ui","category":"anthropic-skill","format":"markdown","content":"---\nname: baseline-ui\ndescription: Validates animation durations, enforces typography scale, checks component accessibility, and prevents layout anti-patterns in Tailwind CSS projects. Use when building UI components, reviewing CSS utilities, styling React views, or enforcing design consistency.\n---\n\n# Baseline UI\n\nEnforces an opinionated UI baseline to prevent AI-generated interface slop.\n\n## How to use\n\n- `/baseline-ui`\n  Apply these constraints to any UI work in this conversation.\n\n- `/baseline-ui <file>`\n  Review the file against all constraints below and output:\n  - violations (quote the exact line/snippet)\n  - why it matters (1 short sentence)\n  - a concrete fix (code-level suggestion)\n\n## Stack\n\n- MUST use Tailwind CSS defaults unless custom values already exist or are explicitly requested\n- MUST use `motion/react` (formerly `framer-motion`) when JavaScript animation is required\n- SHOULD use `tw-animate-css` for entrance and micro-animations in Tailwind CSS\n- MUST use `cn` utility (`clsx` + `tailwind-merge`) for class logic\n\n## Components\n\n- MUST use accessible component primitives for anything with keyboard or focus behavior (`Base UI`, `React Aria`, `Radix`)\n- MUST use the project’s existing component primitives first\n- NEVER mix primitive systems within the same interaction surface\n- SHOULD prefer [`Base UI`](https://base-ui.com/react/components) for new primitives if compatible with the stack\n- MUST add an `aria-label` to icon-only buttons\n- NEVER rebuild keyboard or focus behavior by hand unless explicitly requested\n\n## Interaction\n\n- MUST use an `AlertDialog` for destructive or irreversible actions\n- SHOULD use structural skeletons for loading states\n- NEVER use `h-screen`, use `h-dvh`\n- MUST respect `safe-area-inset` for fixed elements\n- MUST show errors next to where the action happens\n- NEVER block paste in `input` or `textarea` elements\n\n## Animation\n\n- NEVER add animation unless it is explicitly requested\n- MUST animate only compositor props (`transform`, `opacity`)\n- NEVER animate layout properties (`width`, `height`, `top`, `left`, `margin`, `padding`)\n- SHOULD avoid animating paint properties (`background`, `color`) except for small, local UI (text, icons)\n- SHOULD use `ease-out` on entrance\n- NEVER exceed `200ms` for interaction feedback\n- MUST pause looping animations when off-screen\n- SHOULD respect `prefers-reduced-motion`\n- NEVER introduce custom easing curves unless explicitly requested\n- SHOULD avoid animating large images or full-screen surfaces\n\n## Typography\n\n- MUST use `text-balance` for headings and `text-pretty` for body/paragraphs\n- MUST use `tabular-nums` for data\n- SHOULD use `truncate` or `line-clamp` for dense UI\n- NEVER modify `letter-spacing` (`tracking-*`) unless explicitly requested\n\n## Layout\n\n- MUST use a fixed `z-index` scale (no arbitrary `z-*`)\n- SHOULD use `size-*` for square elements instead of `w-*` + `h-*`\n\n## Performance\n\n- NEVER animate large `blur()` or `backdrop-filter` surfaces\n- NEVER apply `will-change` outside an active animation\n- NEVER use `useEffect` for anything that can be expressed as render logic\n\n## Design\n\n- NEVER use gradients unless explicitly requested\n- NEVER use purple or multicolor gradients\n- NEVER use glow effects as primary affordances\n- SHOULD use Tailwind CSS default shadow scale unless explicitly requested\n- MUST give empty states one clear next action\n- SHOULD limit accent color usage to one per view\n- SHOULD use existing theme or Tailwind CSS color tokens before introducing new ones\n","frontmatter":{"name":"baseline-ui","description":"Validates animation durations, enforces typography scale, checks component accessibility, and prevents layout anti-patterns in Tailwind CSS projects. Use when building UI components, reviewing CSS utilities, styling React views, or enforcing design consistency."},"isInternal":false,"tokens":773,"sizeBytes":3531},{"name":"SKILL.md","path":".agents/skills/chat-sdk/SKILL.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/chat-sdk/SKILL.md","title":"chat-sdk","category":"anthropic-skill","format":"markdown","content":"---\nname: chat-sdk\ndescription: >\n  Build multi-platform chat bots with Chat SDK (`chat` npm package). Use when developers want to\n  (1) Build a Slack, Teams, Google Chat, Discord, Telegram, GitHub, Linear, or WhatsApp bot,\n  (2) Use Chat SDK to handle mentions, direct messages, subscribed threads, reactions, slash\n  commands, cards, modals, files, or AI streaming,\n  (3) Set up webhook routes or multi-adapter bots,\n  (4) Send rich cards or streamed AI responses to chat platforms,\n  (5) Build or maintain a custom adapter or state adapter.\n  Triggers on \"chat sdk\", \"chat bot\", \"slack bot\", \"teams bot\", \"google chat bot\", \"discord bot\",\n  \"telegram bot\", \"whatsapp bot\", \"@chat-adapter\", \"@chat-adapter/state-\", \"custom adapter\",\n  \"state adapter\", \"build adapter\", and building bots that work across multiple chat platforms.\n---\n\n# Chat SDK\n\nUnified TypeScript SDK for building chat bots across Slack, Teams, Google Chat, Discord, Telegram, GitHub, Linear, and WhatsApp. Write bot logic once, deploy everywhere.\n\n## Start with published sources\n\nWhen Chat SDK is installed in a user project, inspect the published files that ship in `node_modules`:\n\n```\nnode_modules/chat/docs/                    # bundled docs\nnode_modules/chat/dist/index.d.ts          # core API types\nnode_modules/chat/dist/jsx-runtime.d.ts    # JSX runtime types\nnode_modules/chat/docs/contributing/       # adapter-authoring docs\nnode_modules/chat/docs/guides/             # framework/platform guides\n```\n\nIf one of the paths below does not exist, that package is not installed in the project yet.\n\nRead these before writing code:\n- `node_modules/chat/docs/getting-started.mdx` — install and setup\n- `node_modules/chat/docs/usage.mdx` — `Chat` config and lifecycle\n- `node_modules/chat/docs/handling-events.mdx` — event routing and handlers\n- `node_modules/chat/docs/threads-messages-channels.mdx` — thread/channel/message model\n- `node_modules/chat/docs/posting-messages.mdx` — post, edit, delete, schedule\n- `node_modules/chat/docs/streaming.mdx` — AI SDK integration and streaming semantics\n- `node_modules/chat/docs/cards.mdx` — JSX cards\n- `node_modules/chat/docs/actions.mdx` — button/select interactions\n- `node_modules/chat/docs/modals.mdx` — modal submit/close flows\n- `node_modules/chat/docs/slash-commands.mdx` — slash command routing\n- `node_modules/chat/docs/direct-messages.mdx` — DM behavior and `openDM()`\n- `node_modules/chat/docs/files.mdx` — attachments/uploads\n- `node_modules/chat/docs/state.mdx` — persistence, locking, dedupe\n- `node_modules/chat/docs/adapters.mdx` — cross-platform feature matrix\n- `node_modules/chat/docs/api/chat.mdx` — exact `Chat` API\n- `node_modules/chat/docs/api/thread.mdx` — exact `Thread` API\n- `node_modules/chat/docs/api/message.mdx` — exact `Message` API\n- `node_modules/chat/docs/api/modals.mdx` — modal element and event details\n\nFor the specific adapter or state package you are using, inspect that installed package's `dist/index.d.ts` export surface in `node_modules`.\n\n## Quick start\n\n```typescript\nimport { Chat } from \"chat\";\nimport { createSlackAdapter } from \"@chat-adapter/slack\";\nimport { createRedisState } from \"@chat-adapter/state-redis\";\n\nconst bot = new Chat({\n  userName: \"mybot\",\n  adapters: {\n    slack: createSlackAdapter(),\n  },\n  state: createRedisState(),\n  dedupeTtlMs: 600_000,\n});\n\nbot.onNewMention(async (thread) => {\n  await thread.subscribe();\n  await thread.post(\"Hello! I'm listening to this thread.\");\n});\n\nbot.onSubscribedMessage(async (thread, message) => {\n  await thread.post(`You said: ${message.text}`);\n});\n```\n\n## Core concepts\n\n- **Chat** — main entry point; coordinates adapters, routing, locks, and state\n- **Adapters** — platform-specific integrations for Slack, Teams, Google Chat, Discord, Telegram, GitHub, Linear, and WhatsApp\n- **State adapters** — persistence for subscriptions, locks, dedupe, and thread state\n- **Thread** — conversation context with `post()`, `stream()`, `subscribe()`, `setState()`, `startTyping()`\n- **Message** — normalized content with `text`, `formatted`, attachments, author info, and platform `raw`\n- **Channel** — container for threads and top-level posts\n\n## Event handlers\n\n| Handler | Trigger |\n|---------|---------|\n| `onNewMention` | Bot @-mentioned in an unsubscribed thread |\n| `onDirectMessage` | New DM in an unsubscribed DM thread |\n| `onSubscribedMessage` | Any message in a subscribed thread |\n| `onNewMessage(regex)` | Regex match in an unsubscribed thread |\n| `onReaction(emojis?)` | Emoji added or removed |\n| `onAction(actionIds?)` | Button clicks and select/radio interactions |\n| `onModalSubmit(callbackId?)` | Modal form submitted |\n| `onModalClose(callbackId?)` | Modal dismissed/cancelled |\n| `onSlashCommand(commands?)` | Slash command invocation |\n| `onAssistantThreadStarted` | Slack assistant thread opened |\n| `onAssistantContextChanged` | Slack assistant context changed |\n| `onAppHomeOpened` | Slack App Home opened |\n| `onMemberJoinedChannel` | Slack member joined channel event |\n\nRead `node_modules/chat/docs/handling-events.mdx`, `node_modules/chat/docs/actions.mdx`, `node_modules/chat/docs/modals.mdx`, and `node_modules/chat/docs/slash-commands.mdx` before wiring handlers. `onDirectMessage` behavior is documented in `node_modules/chat/docs/direct-messages.mdx`.\n\n## Streaming\n\nPass any `AsyncIterable<string>` to `thread.post()` or `thread.stream()`. For AI SDK, prefer `result.fullStream` over `result.textStream` when available so step boundaries are preserved.\n\n```typescript\nimport { ToolLoopAgent } from \"ai\";\n\nconst agent = new ToolLoopAgent({ model: \"anthropic/claude-4.5-sonnet\" });\n\nbot.onNewMention(async (thread, message) => {\n  const result = await agent.stream({ prompt: message.text });\n  await thread.post(result.fullStream);\n});\n```\n\nKey details:\n- `streamingUpdateIntervalMs` controls post+edit fallback cadence\n- `fallbackStreamingPlaceholderText` defaults to `\"...\"`; set `null` to disable\n- Structured `StreamChunk` support is Slack-only; other adapters ignore non-text chunks\n\n## Cards and modals (JSX)\n\nSet `jsxImportSource: \"chat\"` in `tsconfig.json`.\n\nCard components:\n- `Card`, `CardText`, `Section`, `Fields`, `Field`, `Button`, `CardLink`, `LinkButton`, `Actions`, `Select`, `SelectOption`, `RadioSelect`, `Table`, `Image`, `Divider`\n\nModal components:\n- `Modal`, `TextInput`, `Select`, `SelectOption`, `RadioSelect`\n\n```tsx\nawait thread.post(\n  <Card title=\"Order #1234\">\n    <CardText>Your order has been received.</CardText>\n    <Actions>\n      <Button id=\"approve\" style=\"primary\">Approve</Button>\n      <Button id=\"reject\" style=\"danger\">Reject</Button>\n    </Actions>\n  </Card>\n);\n```\n\n## Adapter inventory\n\n### Official platform adapters\n\n| Platform | Package | Factory |\n|---------|---------|---------|\n| Slack | `@chat-adapter/slack` | `createSlackAdapter` |\n| Microsoft Teams | `@chat-adapter/teams` | `createTeamsAdapter` |\n| Google Chat | `@chat-adapter/gchat` | `createGoogleChatAdapter` |\n| Discord | `@chat-adapter/discord` | `createDiscordAdapter` |\n| GitHub | `@chat-adapter/github` | `createGitHubAdapter` |\n| Linear | `@chat-adapter/linear` | `createLinearAdapter` |\n| Telegram | `@chat-adapter/telegram` | `createTelegramAdapter` |\n| WhatsApp Business Cloud | `@chat-adapter/whatsapp` | `createWhatsAppAdapter` |\n\n### Official state adapters\n\n| State backend | Package | Factory |\n|--------------|---------|---------|\n| Redis | `@chat-adapter/state-redis` | `createRedisState` |\n| ioredis | `@chat-adapter/state-ioredis` | `createIoRedisState` |\n| PostgreSQL | `@chat-adapter/state-pg` | `createPostgresState` |\n| Memory | `@chat-adapter/state-memory` | `createMemoryState` |\n\n### Community adapters\n\n- `chat-state-cloudflare-do`\n- `@beeper/chat-adapter-matrix`\n- `chat-adapter-imessage`\n- `@bitbasti/chat-adapter-webex`\n- `@resend/chat-sdk-adapter`\n- `chat-adapter-baileys`\n\n### Coming-soon platform entries\n\n- Instagram\n- Signal\n- X\n- Messenger\n\n## Building a custom adapter\n\nRead these published docs first:\n- `node_modules/chat/docs/contributing/building.mdx`\n- `node_modules/chat/docs/contributing/testing.mdx`\n- `node_modules/chat/docs/contributing/publishing.mdx`\n\nAlso inspect:\n- `node_modules/chat/dist/index.d.ts` — `Adapter` and related interfaces\n- `node_modules/@chat-adapter/shared/dist/index.d.ts` — shared errors and utilities\n- Installed official adapter `dist/index.d.ts` files — reference implementations for config and APIs\n\nA custom adapter needs request verification, webhook parsing, message/thread/channel operations, ID encoding/decoding, and a format converter. Use `BaseFormatConverter` from `chat` and shared utilities from `@chat-adapter/shared`.\n\n## Webhook setup\n\nEach registered adapter exposes `bot.webhooks.<name>`. Wire those directly to your HTTP framework routes. See `node_modules/chat/docs/guides/slack-nextjs.mdx` and `node_modules/chat/docs/guides/discord-nuxt.mdx` for framework-specific route patterns.\n","frontmatter":{"name":"chat-sdk","description":"Build multi-platform chat bots with Chat SDK (`chat` npm package). Use when developers want to (1) Build a Slack, Teams, Google Chat, Discord, Telegram, GitHub, Linear, or WhatsApp bot, (2) Use Chat SDK to handle mentions, direct messages, subscribed threads, reactions, slash commands, cards, modals, files, or AI streaming, (3) Set up webhook routes or multi-adapter bots, (4) Send rich cards or streamed AI responses to chat platforms, (5) Build or maintain a custom adapter or state adapter. Triggers on \"chat sdk\", \"chat bot\", \"slack bot\", \"teams bot\", \"google chat bot\", \"discord bot\", \"telegram bot\", \"whatsapp bot\", \"@chat-adapter\", \"@chat-adapter/state-\", \"custom adapter\", \"state adapter\", \"build adapter\", and building bots that work across multiple chat platforms.\n"},"isInternal":false,"tokens":2220,"sizeBytes":8994},{"name":"SKILL.md","path":".agents/skills/code-review/SKILL.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/code-review/SKILL.md","title":"code-review","category":"anthropic-skill","format":"markdown","content":"---\nname: code-review\ndescription: Reviews code changes and provides actionable feedback. Use when the user asks to review a PR, diff, commit, or code changes. Triggers on \"/review\", \"review this PR\", \"review my changes\", \"code review\".\n---\n\nYou are a code reviewer. Your job is to review code changes and provide actionable feedback.\n\n## Determining What to Review\n\nBased on the input provided, determine which type of review to perform:\n\n1. **No arguments (default)**: Review all uncommitted changes\n   - Run: `git diff` for unstaged changes\n   - Run: `git diff --cached` for staged changes\n\n2. **Commit hash** (40-char SHA or short hash): Review that specific commit\n   - Run: `git show $ARGUMENTS`\n\n3. **Branch name**: Compare current branch to the specified branch\n   - Run: `git diff $ARGUMENTS...HEAD`\n\n4. **PR URL or number** (contains \"github.com\" or \"pull\" or looks like a PR number): Review the pull request\n   - Run: `gh pr view $ARGUMENTS` to get PR context\n   - Run: `gh pr diff $ARGUMENTS` to get the diff\n\nUse best judgement when processing input.\n\n---\n\n## Gathering Context\n\n**Diffs alone are not enough.** After getting the diff, read the entire file(s) being modified to understand the full context. Code that looks wrong in isolation may be correct given surrounding logic—and vice versa.\n\n- Use the diff to identify which files changed\n- Read the full file to understand existing patterns, control flow, and error handling\n- When changes touch inputs, auth, storage, networking, rendering, or secrets, trace the trust boundary instead of reviewing the code in isolation\n- Check for existing style guide or conventions files (CONVENTIONS.md, AGENTS.md, .editorconfig, etc.)\n\n---\n\n## What to Look For\n\n**Bugs** - Your primary focus.\n- Logic errors, off-by-one mistakes, incorrect conditionals\n- If-else guards: missing guards, incorrect branching, unreachable code paths\n- Edge cases: null/empty/undefined inputs, error conditions, race conditions\n- Broken error handling that swallows failures, throws unexpectedly or returns error types that are not caught.\n\n**Security / Safety** - Treat this as a first-class review concern, not an afterthought.\n- Assume changed code may be reachable by untrusted users or hostile input unless you can verify otherwise\n- Look for injection, XSS, auth/authz bypass, CSRF, SSRF, open redirects, path traversal, unsafe file access, secret/token exposure, privilege escalation, insecure defaults, and tenant/data isolation leaks\n- Check that validation and authorization happen at the real boundary, not only in the UI or caller\n- Verify sensitive operations fail closed, do not log secrets, and do not expand access beyond the intended actor/resource scope\n- Prefer flagging realistic exploit paths over generic \"security concern\" comments; explain the attacker-controlled input, boundary, and impact\n\n**Structure** - Does the code fit the codebase?\n- Does it follow existing patterns and conventions?\n- Are there established abstractions it should use but doesn't?\n- Excessive nesting that could be flattened with early returns or extraction\n\n**Performance** - Only flag if obviously problematic.\n- O(n²) on unbounded data, N+1 queries, blocking I/O on hot paths\n\n---\n\n## Before You Flag Something\n\n**Be certain.** If you're going to call something a bug, you need to be confident it actually is one.\n\n- Only review the changes - do not review pre-existing code that wasn't modified\n- Don't flag something as a bug if you're unsure - investigate first\n- Don't invent hypothetical problems - if an edge case matters, explain the realistic scenario where it breaks\n- For security findings, describe the concrete exploit path or trust-boundary failure instead of vague risk language\n- If you need more context to be sure, use the tools below to get it\n\n**Don't be a zealot about style.** When checking code against conventions:\n\n- Verify the code is *actually* in violation. Don't complain about else statements if early returns are already being used correctly.\n- Some \"violations\" are acceptable when they're the simplest option. A `let` statement is fine if the alternative is convoluted.\n- Excessive nesting is a legitimate concern regardless of other style choices.\n- Don't flag style preferences as issues unless they clearly violate established project conventions.\n\n---\n\n## Tools\n\nUse these to inform your review:\n\n- **Explore agent** - Find how existing code handles similar problems. Check patterns, conventions, and prior art before claiming something doesn't fit.\n\nIf you're uncertain about something and can't verify it with these tools, say \"I'm not sure about X\" rather than flagging it as a definite issue.\n\n---\n\n## Output\n\n1. If there is a bug, be direct and clear about why it is a bug.\n2. Clearly communicate severity of issues. Do not overstate severity.\n3. Critiques should clearly and explicitly communicate the scenarios, environments, or inputs that are necessary for the bug to arise. The comment should immediately indicate that the issue's severity depends on these factors.\n4. For security findings, explicitly state the attacker-controlled input, missing control, and concrete impact.\n5. Your tone should be matter-of-fact and not accusatory or overly positive. It should read as a helpful AI assistant suggestion without sounding too much like a human reviewer.\n6. Write so the reader can quickly understand the issue without reading too closely.\n7. AVOID flattery, do not give any comments that are not helpful to the reader. Avoid phrasing like \"Great job ...\", \"Thanks for ...\".\n","frontmatter":{"name":"code-review","description":"Reviews code changes and provides actionable feedback. Use when the user asks to review a PR, diff, commit, or code changes. Triggers on \"/review\", \"review this PR\", \"review my changes\", \"code review\"."},"isInternal":false,"tokens":1198,"sizeBytes":5567},{"name":"SKILL.md","path":".agents/skills/deploy-open-harness/SKILL.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/deploy-open-harness/SKILL.md","title":"deploy-open-harness","category":"anthropic-skill","format":"markdown","content":"---\nname: deploy-open-harness\ndescription: Guides a user through collecting the credentials needed to deploy their own copy of Open Harness, deploying this repo on Vercel, and completing first-run setup. Use for requests about deploying, self-hosting, configuring credentials, or getting started with a fork of this app.\n---\n\nYou are helping a user deploy their own copy of Open Harness.\n\nBase your guidance on the current codebase, not on older Harness-era setup assumptions.\n\n## First rule: verify current requirements from the repo\n\nBefore giving deployment advice, read these files if you have not already:\n\n- `README.md`\n- `apps/web/.env.example`\n- `apps/web/lib/db/client.ts`\n- `apps/web/lib/jwe/encrypt.ts`\n- `apps/web/lib/crypto.ts`\n- `apps/web/app/api/auth/signin/vercel/route.ts`\n- `apps/web/app/api/auth/vercel/callback/route.ts`\n- `apps/web/app/api/github/app/install/route.ts`\n- `apps/web/app/api/github/app/callback/route.ts`\n- `apps/web/lib/github/app-auth.ts`\n- `apps/web/lib/redis.ts`\n- `apps/web/lib/sandbox/config.ts`\n\nIf the code and the docs disagree, trust the code and say so.\n\nDo not rely on `scripts/setup.sh`.\n\n## Goals\n\nHelp the user:\n\n1. Decide whether they want a minimal deploy or the full GitHub-enabled coding-agent flow.\n2. Collect only the credentials actually required for that scope.\n3. Understand where to obtain each credential.\n4. Deploy this repo on Vercel.\n5. Complete first-run verification.\n6. Leave with a short next-steps checklist.\n\n## Safety rules\n\n- Never ask the user to paste secrets into chat.\n- Tell them where each value belongs, but keep secret values in Vercel project env vars or local env files.\n- Separate blockers for a minimal deploy from blockers for the full GitHub-enabled flow.\n- Be explicit when something is optional.\n\n## Scope the deployment first\n\nStart by determining which path the user wants:\n\n### 1) Minimal deploy\nA working hosted app where the user can deploy it, sign in with Vercel, and use the product without GitHub repo access.\n\n### 2) Full deploy\nEverything in the minimal deploy, plus GitHub account linking, GitHub App installation, private repo access, pushes, and PR creation.\n\nIf the user is unsure, recommend **minimal deploy first**, then layer on GitHub.\n\n## Credential checklist\n\nUse this checklist when guiding the user.\n\n### Required for the app to run\n\n- `POSTGRES_URL`\n- `JWE_SECRET`\n\n### Required for a usable hosted deployment\n\n- `ENCRYPTION_KEY`\n- `NEXT_PUBLIC_VERCEL_APP_CLIENT_ID`\n- `VERCEL_APP_CLIENT_SECRET`\n\n### Required for GitHub-enabled repo flows\n\n- `NEXT_PUBLIC_GITHUB_CLIENT_ID`\n- `GITHUB_CLIENT_SECRET`\n- `GITHUB_APP_ID`\n- `GITHUB_APP_PRIVATE_KEY`\n- `NEXT_PUBLIC_GITHUB_APP_SLUG`\n- `GITHUB_WEBHOOK_SECRET`\n\n### Optional\n\n- `REDIS_URL` or `KV_URL`\n- `VERCEL_PROJECT_PRODUCTION_URL`\n- `NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL`\n- `VERCEL_SANDBOX_BASE_SNAPSHOT_ID`\n- `ELEVENLABS_API_KEY`\n\n## How to explain each credential\n\n### PostgreSQL\nTell the user to create a Postgres database and copy the connection string into `POSTGRES_URL`.\n\n### JWE secret\nExplain that this is required for session encryption.\n\nRecommended generation command:\n\n```bash\nopenssl rand -base64 32 | tr '+/' '-_' | tr -d '=\\n'\n```\n\n### Encryption key\nExplain that provider tokens are encrypted at rest and the value must be a 64-character hex string.\n\nRecommended generation command:\n\n```bash\nopenssl rand -hex 32\n```\n\n### Vercel OAuth app\nTell the user to create a Vercel OAuth app and set:\n\n- Callback URL: `https://YOUR_DOMAIN/api/auth/vercel/callback`\n- For local dev: `http://localhost:3000/api/auth/vercel/callback`\n\nStore the credentials as:\n\n- `NEXT_PUBLIC_VERCEL_APP_CLIENT_ID`\n- `VERCEL_APP_CLIENT_SECRET`\n\n### GitHub App\nTell the user they do not need a separate GitHub OAuth app. Open Harness uses the GitHub App's user authorization flow.\n\nTell the user to create a GitHub App and set:\n\n- Homepage URL: `https://YOUR_DOMAIN`\n- Callback URL: `https://YOUR_DOMAIN/api/github/app/callback`\n- Setup URL: `https://YOUR_DOMAIN/api/github/app/callback`\n- For local dev: homepage `http://localhost:3000`, callback/setup `http://localhost:3000/api/github/app/callback`\n\nAlso tell them to:\n\n- enable \"Request user authorization (OAuth) during installation\"\n- use the GitHub App Client ID and Client Secret for `NEXT_PUBLIC_GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET`\n- make the app public if they want org installs to work cleanly\n- generate a webhook secret\n- download/generate the private key\n\nStore the values as:\n\n- `NEXT_PUBLIC_GITHUB_CLIENT_ID`\n- `GITHUB_CLIENT_SECRET`\n- `GITHUB_APP_ID`\n- `GITHUB_APP_PRIVATE_KEY`\n- `NEXT_PUBLIC_GITHUB_APP_SLUG`\n- `GITHUB_WEBHOOK_SECRET`\n\nMention that `GITHUB_APP_PRIVATE_KEY` can be stored either as PEM contents with escaped newlines or as a base64-encoded PEM.\n\n### Redis / KV\nExplain that Redis is optional. It improves resumable streams, stop signaling, and caching, but it is not required for the first deploy.\n\n## Deployment flow\n\nGuide the user through this sequence:\n\n1. Fork the repo.\n2. Import it into Vercel at the repo root.\n3. Add the baseline env vars:\n   - `POSTGRES_URL`\n   - `JWE_SECRET`\n   - `ENCRYPTION_KEY`\n4. Deploy once to get a stable production URL.\n5. Create the Vercel OAuth app using that production URL.\n6. Add `NEXT_PUBLIC_VERCEL_APP_CLIENT_ID` and `VERCEL_APP_CLIENT_SECRET`.\n7. Redeploy.\n8. If the user wants the full GitHub flow, create the GitHub App using the production URL, add the GitHub env vars, and redeploy again.\n9. Optionally add Redis/KV and the production URL vars.\n\nIf the user already has a custom domain ready, it is fine to use that domain from the start instead of the default `vercel.app` production URL.\n\n## First-run verification\n\nFor a minimal deploy, walk the user through:\n\n1. Open the production site.\n2. Sign in with Vercel.\n3. Confirm they land in the app successfully.\n4. Create a session and confirm the basic UI loads.\n\nFor the full deploy, also verify:\n\n1. GitHub account linking works.\n2. GitHub App installation completes.\n3. Installations or repos appear in the UI.\n4. A repo-backed session can start.\n5. The sandbox starts and the agent can work in the repo.\n\nIf something fails, identify the missing credential or callback mismatch instead of giving generic advice.\n\n## Response format\n\nWhen helping a user, prefer this structure:\n\n1. **Target scope** — minimal or full.\n2. **Credential checklist** — grouped into required now vs optional later.\n3. **How to get each missing credential** — short, concrete instructions.\n4. **Deploy steps** — only the next actions the user should take.\n5. **Verification** — what to click/test after deploy.\n6. **Next upgrades** — Redis, GitHub, voice, custom domain, snapshot override, only if relevant.\n\nBe concise. Keep the user moving toward the next unblocker.","frontmatter":{"name":"deploy-open-harness","description":"Guides a user through collecting the credentials needed to deploy their own copy of Open Harness, deploying this repo on Vercel, and completing first-run setup. Use for requests about deploying, self-hosting, configuring credentials, or getting started with a fork of this app."},"isInternal":false,"tokens":1606,"sizeBytes":6803},{"name":"SKILL.md","path":".agents/skills/emil-design-eng/SKILL.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/emil-design-eng/SKILL.md","title":"emil-design-eng","category":"anthropic-skill","format":"markdown","content":"---\nname: emil-design-eng\ndescription: This skill encodes Emil Kowalski's philosophy on UI polish, component design, animation decisions, and the invisible details that make software feel great.\n---\n\n# Design Engineering\n\nYou are a design engineer with the craft sensibility. You build interfaces where every detail compounds into something that feels right. You understand that in a world where everyone's software is good enough, taste is the differentiator.\n\n## Core Philosophy\n\n### Taste is trained, not innate\n\nGood taste is not personal preference. It is a trained instinct: the ability to see beyond the obvious and recognize what elevates. You develop it by surrounding yourself with great work, thinking deeply about why something feels good, and practicing relentlessly.\n\nWhen building UI, don't just make it work. Study why the best interfaces feel the way they do. Reverse engineer animations. Inspect interactions. Be curious.\n\n### Unseen details compound\n\nMost details users never consciously notice. That is the point. When a feature functions exactly as someone assumes it should, they proceed without giving it a second thought. That is the goal.\n\n> \"All those unseen details combine to produce something that's just stunning, like a thousand barely audible voices all singing in tune.\" - Paul Graham\n\nEvery decision below exists because the aggregate of invisible correctness creates interfaces people love without knowing why.\n\n### Beauty is leverage\n\nPeople select tools based on the overall experience, not just functionality. Good defaults and good animations are real differentiators. Beauty is underutilized in software. Use it as leverage to stand out.\n\n## Review Format (Required)\n\nWhen reviewing UI code, you MUST use a markdown table with Before/After columns. Do NOT use a list with \"Before:\" and \"After:\" on separate lines. Always output an actual markdown table like this:\n\n| Before                                | After                                                             | Why                                                                          |\n| ------------------------------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------- |\n| `transition: all 300ms`               | `transition: transform 200ms ease-out`                            | Specify exact properties; avoid `all`                                        |\n| `transform: scale(0)`                 | `transform: scale(0.95); opacity: 0`                              | Nothing in the real world appears from nothing                               |\n| `ease-in` on dropdown                 | `ease-out` with custom curve                                      | `ease-in` feels sluggish; `ease-out` gives instant feedback                  |\n| No `:active` state on button          | `transform: scale(0.97)` on `:active`                             | Buttons must feel responsive to press                                        |\n| `transform-origin: center` on popover | `transform-origin: var(--radix-popover-content-transform-origin)` | Popovers should scale from their trigger (not modals — modals stay centered) |\n\nWrong format (never do this):\n\n```\nBefore: transition: all 300ms\nAfter: transition: transform 200ms ease-out\n────────────────────────────\nBefore: scale(0)\nAfter: scale(0.95)\n```\n\nCorrect format: A single markdown table with | Before | After | Why | columns, one row per issue found. The \"Why\" column briefly explains the reasoning.\n\n## The Animation Decision Framework\n\nBefore writing any animation code, answer these questions in order:\n\n### 1. Should this animate at all?\n\n**Ask:** How often will users see this animation?\n\n| Frequency                                                   | Decision                     |\n| ----------------------------------------------------------- | ---------------------------- |\n| 100+ times/day (keyboard shortcuts, command palette toggle) | No animation. Ever.          |\n| Tens of times/day (hover effects, list navigation)          | Remove or drastically reduce |\n| Occasional (modals, drawers, toasts)                        | Standard animation           |\n| Rare/first-time (onboarding, feedback forms, celebrations)  | Can add delight              |\n\n**Never animate keyboard-initiated actions.** These actions are repeated hundreds of times daily. Animation makes them feel slow, delayed, and disconnected from the user's actions.\n\nRaycast has no open/close animation. That is the optimal experience for something used hundreds of times a day.\n\n### 2. What is the purpose?\n\nEvery animation must have a clear answer to \"why does this animate?\"\n\nValid purposes:\n\n- **Spatial consistency**: toast enters and exits from the same direction, making swipe-to-dismiss feel intuitive\n- **State indication**: a morphing feedback button shows the state change\n- **Explanation**: a marketing animation that shows how a feature works\n- **Feedback**: a button scales down on press, confirming the interface heard the user\n- **Preventing jarring changes**: elements appearing or disappearing without transition feel broken\n\nIf the purpose is just \"it looks cool\" and the user will see it often, don't animate.\n\n### 3. What easing should it use?\n\nIs the element entering or exiting?\nYes → ease-out (starts fast, feels responsive)\nNo →\nIs it moving/morphing on screen?\nYes → ease-in-out (natural acceleration/deceleration)\nIs it a hover/color change?\nYes → ease\nIs it constant motion (marquee, progress bar)?\nYes → linear\nDefault → ease-out\n\n**Critical: use custom easing curves.** The built-in CSS easings are too weak. They lack the punch that makes animations feel intentional.\n\n```css\n/* Strong ease-out for UI interactions */\n--ease-out: cubic-bezier(0.23, 1, 0.32, 1);\n\n/* Strong ease-in-out for on-screen movement */\n--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);\n\n/* iOS-like drawer curve (from Ionic Framework) */\n--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);\n```\n\n**Never use ease-in for UI animations.** It starts slow, which makes the interface feel sluggish and unresponsive. A dropdown with `ease-in` at 300ms _feels_ slower than `ease-out` at the same 300ms, because ease-in delays the initial movement — the exact moment the user is watching most closely.\n\n**Easing curve resources:** Don't create curves from scratch. Use [easing.dev](https://easing.dev/) or [easings.co](https://easings.co/) to find stronger custom variants of standard easings.\n\n### 4. How fast should it be?\n\n| Element                  | Duration      |\n| ------------------------ | ------------- |\n| Button press feedback    | 100-160ms     |\n| Tooltips, small popovers | 125-200ms     |\n| Dropdowns, selects       | 150-250ms     |\n| Modals, drawers          | 200-500ms     |\n| Marketing/explanatory    | Can be longer |\n\n**Rule: UI animations should stay under 300ms.** A 180ms dropdown feels more responsive than a 400ms one. A faster-spinning spinner makes the app feel like it loads faster, even when the load time is identical.\n\n### Perceived performance\n\nSpeed in animation is not just about feeling snappy — it directly affects how users perceive your app's performance:\n\n- A **fast-spinning spinner** makes loading feel faster (same load time, different perception)\n- A **180ms select** animation feels more responsive than a **400ms** one\n- **Instant tooltips** after the first one is open (skip delay + skip animation) make the whole toolbar feel faster\n\nThe perception of speed matters as much as actual speed. Easing amplifies this: `ease-out` at 200ms _feels_ faster than `ease-in` at 200ms because the user sees immediate movement.\n\n## Spring Animations\n\nSprings feel more natural than duration-based animations because they simulate real physics. They don't have fixed durations — they settle based on physical parameters.\n\n### When to use springs\n\n- Drag interactions with momentum\n- Elements that should feel \"alive\" (like Apple's Dynamic Island)\n- Gestures that can be interrupted mid-animation\n- Decorative mouse-tracking interactions\n\n### Spring-based mouse interactions\n\nTying visual changes directly to mouse position feels artificial because it lacks motion. Use `useSpring` from Motion (formerly Framer Motion) to interpolate value changes with spring-like behavior instead of updating immediately.\n\n```jsx\nimport { useSpring } from \"framer-motion\";\n\n// Without spring: feels artificial, instant\nconst rotation = mouseX * 0.1;\n\n// With spring: feels natural, has momentum\nconst springRotation = useSpring(mouseX * 0.1, {\n  stiffness: 100,\n  damping: 10,\n});\n```\n\nThis works because the animation is **decorative** — it doesn't serve a function. If this were a functional graph in a banking app, no animation would be better. Know when decoration helps and when it hinders.\n\n### Spring configuration\n\n**Apple's approach (recommended — easier to reason about):**\n\n```js\n{ type: \"spring\", duration: 0.5, bounce: 0.2 }\n```\n\n**Traditional physics (more control):**\n\n```js\n{ type: \"spring\", mass: 1, stiffness: 100, damping: 10 }\n```\n\nKeep bounce subtle (0.1-0.3) when used. Avoid bounce in most UI contexts. Use it for drag-to-dismiss and playful interactions.\n\n### Interruptibility advantage\n\nSprings maintain velocity when interrupted — CSS animations and keyframes restart from zero. This makes springs ideal for gestures users might change mid-motion. When you click an expanded item and quickly press Escape, a spring-based animation smoothly reverses from its current position.\n\n## Component Building Principles\n\n### Buttons must feel responsive\n\nAdd `transform: scale(0.97)` on `:active`. This gives instant feedback, making the UI feel like it is truly listening to the user.\n\n```css\n.button {\n  transition: transform 160ms ease-out;\n}\n\n.button:active {\n  transform: scale(0.97);\n}\n```\n\nThis applies to any pressable element. The scale should be subtle (0.95-0.98).\n\n### Never animate from scale(0)\n\nNothing in the real world disappears and reappears completely. Elements animating from `scale(0)` look like they come out of nowhere.\n\nStart from `scale(0.9)` or higher, combined with opacity. Even a barely-visible initial scale makes the entrance feel more natural, like a balloon that has a visible shape even when deflated.\n\n```css\n/* Bad */\n.entering {\n  transform: scale(0);\n}\n\n/* Good */\n.entering {\n  transform: scale(0.95);\n  opacity: 0;\n}\n```\n\n### Make popovers origin-aware\n\nPopovers should scale in from their trigger, not from center. The default `transform-origin: center` is wrong for almost every popover. **Exception: modals.** Modals should keep `transform-origin: center` because they are not anchored to a specific trigger — they appear centered in the viewport.\n\n```css\n/* Radix UI */\n.popover {\n  transform-origin: var(--radix-popover-content-transform-origin);\n}\n\n/* Base UI */\n.popover {\n  transform-origin: var(--transform-origin);\n}\n```\n\nWhether the user notices the difference individually does not matter. In the aggregate, unseen details become visible. They compound.\n\n### Tooltips: skip delay on subsequent hovers\n\nTooltips should delay before appearing to prevent accidental activation. But once one tooltip is open, hovering over adjacent tooltips should open them instantly with no animation. This feels faster without defeating the purpose of the initial delay.\n\n```css\n.tooltip {\n  transition:\n    transform 125ms ease-out,\n    opacity 125ms ease-out;\n  transform-origin: var(--transform-origin);\n}\n\n.tooltip[data-starting-style],\n.tooltip[data-ending-style] {\n  opacity: 0;\n  transform: scale(0.97);\n}\n\n/* Skip animation on subsequent tooltips */\n.tooltip[data-instant] {\n  transition-duration: 0ms;\n}\n```\n\n### Use CSS transitions over keyframes for interruptible UI\n\nCSS transitions can be interrupted and retargeted mid-animation. Keyframes restart from zero. For any interaction that can be triggered rapidly (adding toasts, toggling states), transitions produce smoother results.\n\n```css\n/* Interruptible - good for UI */\n.toast {\n  transition: transform 400ms ease;\n}\n\n/* Not interruptible - avoid for dynamic UI */\n@keyframes slideIn {\n  from {\n    transform: translateY(100%);\n  }\n  to {\n    transform: translateY(0);\n  }\n}\n```\n\n### Use blur to mask imperfect transitions\n\nWhen a crossfade between two states feels off despite trying different easings and durations, add subtle `filter: blur(2px)` during the transition.\n\n**Why blur works:** Without blur, you see two distinct objects during a crossfade — the old state and the new state overlapping. This looks unnatural. Blur bridges the visual gap by blending the two states together, tricking the eye into perceiving a single smooth transformation instead of two objects swapping.\n\nCombine blur with scale-on-press (`scale(0.97)`) for a polished button state transition:\n\n```css\n.button {\n  transition: transform 160ms ease-out;\n}\n\n.button:active {\n  transform: scale(0.97);\n}\n\n.button-content {\n  transition:\n    filter 200ms ease,\n    opacity 200ms ease;\n}\n\n.button-content.transitioning {\n  filter: blur(2px);\n  opacity: 0.7;\n}\n```\n\nKeep blur under 20px. Heavy blur is expensive, especially in Safari.\n\n### Animate enter states with @starting-style\n\nThe modern CSS way to animate element entry without JavaScript:\n\n```css\n.toast {\n  opacity: 1;\n  transform: translateY(0);\n  transition:\n    opacity 400ms ease,\n    transform 400ms ease;\n\n  @starting-style {\n    opacity: 0;\n    transform: translateY(100%);\n  }\n}\n```\n\nThis replaces the common React pattern of using `useEffect` to set `mounted: true` after initial render. Use `@starting-style` when browser support allows; fall back to the `data-mounted` attribute pattern otherwise.\n\n```jsx\n// Legacy pattern (still works everywhere)\nuseEffect(() => {\n  setMounted(true);\n}, []);\n// <div data-mounted={mounted}>\n```\n\n## CSS Transform Mastery\n\n### translateY with percentages\n\nPercentage values in `translate()` are relative to the element's own size. Use `translateY(100%)` to move an element by its own height, regardless of actual dimensions. This is how Sonner positions toasts and how Vaul hides the drawer before animating in.\n\n```css\n/* Works regardless of drawer height */\n.drawer-hidden {\n  transform: translateY(100%);\n}\n\n/* Works regardless of toast height */\n.toast-enter {\n  transform: translateY(-100%);\n}\n```\n\nPrefer percentages over hardcoded pixel values. They are less error-prone and adapt to content.\n\n### scale() scales children too\n\nUnlike `width`/`height`, `scale()` also scales an element's children. When scaling a button on press, the font size, icons, and content scale proportionally. This is a feature, not a bug.\n\n### 3D transforms for depth\n\n`rotateX()`, `rotateY()` with `transform-style: preserve-3d` create real 3D effects in CSS. Orbiting animations, coin flips, and depth effects are all possible without JavaScript.\n\n```css\n.wrapper {\n  transform-style: preserve-3d;\n}\n\n@keyframes orbit {\n  from {\n    transform: translate(-50%, -50%) rotateY(0deg) translateZ(72px)\n      rotateY(360deg);\n  }\n  to {\n    transform: translate(-50%, -50%) rotateY(360deg) translateZ(72px)\n      rotateY(0deg);\n  }\n}\n```\n\n### transform-origin\n\nEvery element has an anchor point from which transforms execute. The default is center. Set it to match where the trigger lives for origin-aware interactions.\n\n## clip-path for Animation\n\n`clip-path` is not just for shapes. It is one of the most powerful animation tools in CSS.\n\n### The inset shape\n\n`clip-path: inset(top right bottom left)` defines a rectangular clipping region. Each value \"eats\" into the element from that side.\n\n```css\n/* Fully hidden from right */\n.hidden {\n  clip-path: inset(0 100% 0 0);\n}\n\n/* Fully visible */\n.visible {\n  clip-path: inset(0 0 0 0);\n}\n\n/* Reveal from left to right */\n.overlay {\n  clip-path: inset(0 100% 0 0);\n  transition: clip-path 200ms ease-out;\n}\n.button:active .overlay {\n  clip-path: inset(0 0 0 0);\n  transition: clip-path 2s linear;\n}\n```\n\n### Tabs with perfect color transitions\n\nDuplicate the tab list. Style the copy as \"active\" (different background, different text color). Clip the copy so only the active tab is visible. Animate the clip on tab change. This creates a seamless color transition that timing individual color transitions can never achieve.\n\n### Hold-to-delete pattern\n\nUse `clip-path: inset(0 100% 0 0)` on a colored overlay. On `:active`, transition to `inset(0 0 0 0)` over 2s with linear timing. On release, snap back with 200ms ease-out. Add `scale(0.97)` on the button for press feedback.\n\n### Image reveals on scroll\n\nStart with `clip-path: inset(0 0 100% 0)` (hidden from bottom). Animate to `inset(0 0 0 0)` when the element enters the viewport. Use `IntersectionObserver` or Framer Motion's `useInView` with `{ once: true, margin: \"-100px\" }`.\n\n### Comparison sliders\n\nOverlay two images. Clip the top one with `clip-path: inset(0 50% 0 0)`. Adjust the right inset value based on drag position. No extra DOM elements needed, fully hardware-accelerated.\n\n## Gesture and Drag Interactions\n\n### Momentum-based dismissal\n\nDon't require dragging past a threshold. Calculate velocity: `Math.abs(dragDistance) / elapsedTime`. If velocity exceeds ~0.11, dismiss regardless of distance. A quick flick should be enough.\n\n```js\nconst timeTaken = new Date().getTime() - dragStartTime.current.getTime();\nconst velocity = Math.abs(swipeAmount) / timeTaken;\n\nif (Math.abs(swipeAmount) >= SWIPE_THRESHOLD || velocity > 0.11) {\n  dismiss();\n}\n```\n\n### Damping at boundaries\n\nWhen a user drags past the natural boundary (e.g., dragging a drawer up when already at top), apply damping. The more they drag, the less the element moves. Things in real life don't suddenly stop; they slow down first.\n\n### Pointer capture for drag\n\nOnce dragging starts, set the element to capture all pointer events. This ensures dragging continues even if the pointer leaves the element bounds.\n\n### Multi-touch protection\n\nIgnore additional touch points after the initial drag begins. Without this, switching fingers mid-drag causes the element to jump to the new position.\n\n```js\nfunction onPress() {\n  if (isDragging) return;\n  // Start drag...\n}\n```\n\n### Friction instead of hard stops\n\nInstead of preventing upward drag entirely, allow it with increasing friction. It feels more natural than hitting an invisible wall.\n\n## Performance Rules\n\n### Only animate transform and opacity\n\nThese properties skip layout and paint, running on the GPU. Animating `padding`, `margin`, `height`, or `width` triggers all three rendering steps.\n\n### CSS variables are inheritable\n\nChanging a CSS variable on a parent recalculates styles for all children. In a drawer with many items, updating `--swipe-amount` on the container causes expensive style recalculation. Update `transform` directly on the element instead.\n\n```js\n// Bad: triggers recalc on all children\nelement.style.setProperty(\"--swipe-amount\", `${distance}px`);\n\n// Good: only affects this element\nelement.style.transform = `translateY(${distance}px)`;\n```\n\n### Framer Motion hardware acceleration caveat\n\nFramer Motion's shorthand properties (`x`, `y`, `scale`) are NOT hardware-accelerated. They use `requestAnimationFrame` on the main thread. For hardware acceleration, use the full `transform` string:\n\n```jsx\n// NOT hardware accelerated (convenient but drops frames under load)\n<motion.div animate={{ x: 100 }} />\n\n// Hardware accelerated (stays smooth even when main thread is busy)\n<motion.div animate={{ transform: \"translateX(100px)\" }} />\n```\n\nThis matters when the browser is simultaneously loading content, running scripts, or painting. At Vercel, the dashboard tab animation used Shared Layout Animations and dropped frames during page loads. Switching to CSS animations (off main thread) fixed it.\n\n### CSS animations beat JS under load\n\nCSS animations run off the main thread. When the browser is busy loading a new page, Framer Motion animations (using `requestAnimationFrame`) drop frames. CSS animations remain smooth. Use CSS for predetermined animations; JS for dynamic, interruptible ones.\n\n### Use WAAPI for programmatic CSS animations\n\nThe Web Animations API gives you JavaScript control with CSS performance. Hardware-accelerated, interruptible, and no library needed.\n\n```js\nelement.animate(\n  [{ clipPath: \"inset(0 0 100% 0)\" }, { clipPath: \"inset(0 0 0 0)\" }],\n  {\n    duration: 1000,\n    fill: \"forwards\",\n    easing: \"cubic-bezier(0.77, 0, 0.175, 1)\",\n  },\n);\n```\n\n## Accessibility\n\n### prefers-reduced-motion\n\nAnimations can cause motion sickness. Reduced motion means fewer and gentler animations, not zero. Keep opacity and color transitions that aid comprehension. Remove movement and position animations.\n\n```css\n@media (prefers-reduced-motion: reduce) {\n  .element {\n    animation: fade 0.2s ease;\n    /* No transform-based motion */\n  }\n}\n```\n\n```jsx\nconst shouldReduceMotion = useReducedMotion();\nconst closedX = shouldReduceMotion ? 0 : \"-100%\";\n```\n\n### Touch device hover states\n\n```css\n@media (hover: hover) and (pointer: fine) {\n  .element:hover {\n    transform: scale(1.05);\n  }\n}\n```\n\nTouch devices trigger hover on tap, causing false positives. Gate hover animations behind this media query.\n\n## The Sonner Principles (Building Loved Components)\n\nThese principles come from building Sonner (13M+ weekly npm downloads) and apply to any component:\n\n1. **Developer experience is key.** No hooks, no context, no complex setup. Insert `<Toaster />` once, call `toast()` from anywhere. The less friction to adopt, the more people will use it.\n\n2. **Good defaults matter more than options.** Ship beautiful out of the box. Most users never customize. The default easing, timing, and visual design should be excellent.\n\n3. **Naming creates identity.** \"Sonner\" (French for \"to ring\") feels more elegant than \"react-toast\". Sacrifice discoverability for memorability when appropriate.\n\n4. **Handle edge cases invisibly.** Pause toast timers when the tab is hidden. Fill gaps between stacked toasts with pseudo-elements to maintain hover state. Capture pointer events during drag. Users never notice these, and that is exactly right.\n\n5. **Use transitions, not keyframes, for dynamic UI.** Toasts are added rapidly. Keyframes restart from zero on interruption. Transitions retarget smoothly.\n\n6. **Build a great documentation site.** Let people touch the product, play with it, and understand it before they use it. Interactive examples with ready-to-use code snippets lower the barrier to adoption.\n\n### Cohesion matters\n\nSonner's animation feels satisfying partly because the whole experience is cohesive. The easing and duration fit the vibe of the library. It is slightly slower than typical UI animations and uses `ease` rather than `ease-out` to feel more elegant. The animation style matches the toast design, the page design, the name — everything is in harmony.\n\nWhen choosing animation values, consider the personality of the component. A playful component can be bouncier. A professional dashboard should be crisp and fast. Match the motion to the mood.\n\n### The opacity + height combination\n\nWhen items enter and exit a list (like Family's drawer), the opacity change must work well with the height animation. This is often trial and error. There is no formula — you adjust until it feels right.\n\n### Review your work the next day\n\nReview animations with fresh eyes. You notice imperfections the next day that you missed during development. Play animations in slow motion or frame by frame to spot timing issues that are invisible at full speed.\n\n### Asymmetric enter/exit timing\n\nPressing should be slow when it needs to be deliberate (hold-to-delete: 2s linear), but release should always be snappy (200ms ease-out). This pattern applies broadly: slow where the user is deciding, fast where the system is responding.\n\n```css\n/* Release: fast */\n.overlay {\n  transition: clip-path 200ms ease-out;\n}\n\n/* Press: slow and deliberate */\n.button:active .overlay {\n  transition: clip-path 2s linear;\n}\n```\n\n## Stagger Animations\n\nWhen multiple elements enter together, stagger their appearance. Each element animates in with a small delay after the previous one. This creates a cascading effect that feels more natural than everything appearing at once.\n\n```css\n.item {\n  opacity: 0;\n  transform: translateY(8px);\n  animation: fadeIn 300ms ease-out forwards;\n}\n\n.item:nth-child(1) {\n  animation-delay: 0ms;\n}\n.item:nth-child(2) {\n  animation-delay: 50ms;\n}\n.item:nth-child(3) {\n  animation-delay: 100ms;\n}\n.item:nth-child(4) {\n  animation-delay: 150ms;\n}\n\n@keyframes fadeIn {\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n```\n\nKeep stagger delays short (30-80ms between items). Long delays make the interface feel slow. Stagger is decorative — never block interaction while stagger animations are playing.\n\n## Debugging Animations\n\n### Slow motion testing\n\nPlay animations at reduced speed to spot issues invisible at full speed. Temporarily increase duration to 2-5x normal, or use browser DevTools animation inspector to slow playback.\n\nThings to look for in slow motion:\n\n- Do colors transition smoothly, or do you see two distinct states overlapping?\n- Does the easing feel right, or does it start/stop abruptly?\n- Is the transform-origin correct, or does the element scale from the wrong point?\n- Are multiple animated properties (opacity, transform, color) in sync?\n\n### Frame-by-frame inspection\n\nStep through animations frame by frame in Chrome DevTools (Animations panel). This reveals timing issues between coordinated properties that you cannot see at full speed.\n\n### Test on real devices\n\nFor touch interactions (drawers, swipe gestures), test on physical devices. Connect your phone via USB, visit your local dev server by IP address, and use Safari's remote devtools. The Xcode Simulator is an alternative but real hardware is better for gesture testing.\n\n## Review Checklist\n\nWhen reviewing UI code, check for:\n\n| Issue                                  | Fix                                                                                           |\n| -------------------------------------- | --------------------------------------------------------------------------------------------- |\n| `transition: all`                      | Specify exact properties: `transition: transform 200ms ease-out`                              |\n| `scale(0)` entry animation             | Start from `scale(0.95)` with `opacity: 0`                                                    |\n| `ease-in` on UI element                | Switch to `ease-out` or custom curve                                                          |\n| `transform-origin: center` on popover  | Set to trigger location or use Radix/Base UI CSS variable (modals are exempt — keep centered) |\n| Animation on keyboard action           | Remove animation entirely                                                                     |\n| Duration > 300ms on UI element         | Reduce to 150-250ms                                                                           |\n| Hover animation without media query    | Add `@media (hover: hover) and (pointer: fine)`                                               |\n| Keyframes on rapidly-triggered element | Use CSS transitions for interruptibility                                                      |\n| Framer Motion `x`/`y` props under load | Use `transform: \"translateX()\"` for hardware acceleration                                     |\n| Same enter/exit transition speed       | Make exit faster than enter (e.g., enter 2s, exit 200ms)                                      |\n| Elements all appear at once            | Add stagger delay (30-80ms between items)                                                     |\n","frontmatter":{"name":"emil-design-eng","description":"This skill encodes Emil Kowalski's philosophy on UI polish, component design, animation decisions, and the invisible details that make software feel great."},"isInternal":false,"tokens":6034,"sizeBytes":27872},{"name":"SKILL.md","path":".agents/skills/frontend-design/SKILL.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/frontend-design/SKILL.md","title":"frontend-design","category":"anthropic-skill","format":"markdown","content":"---\nname: frontend-design\ndescription: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.\n---\n\nThis skill guides creation of distinctive, production-grade frontend interfaces that avoid generic \"AI slop\" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.\n\nThe user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.\n\n## Design Thinking\n\nBefore coding, understand the context and commit to a BOLD aesthetic direction:\n- **Purpose**: What problem does this interface solve? Who uses it?\n- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.\n- **Constraints**: Technical requirements (framework, performance, accessibility).\n- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?\n\n**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.\n\nThen implement working code (HTML/CSS/JS, React, Vue, etc.) that is:\n- Production-grade and functional\n- Visually striking and memorable\n- Cohesive with a clear aesthetic point-of-view\n- Meticulously refined in every detail\n\n## Frontend Aesthetics Guidelines\n\nFocus on:\n- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.\n- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.\n- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.\n- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.\n- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.\n\nNEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.\n\nInterpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.\n\n**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.\n\nRemember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.\n","frontmatter":{"name":"frontend-design","description":"Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics."},"isInternal":false,"tokens":850,"sizeBytes":4401},{"name":"SKILL.md","path":".agents/skills/plan-mode/SKILL.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/plan-mode/SKILL.md","title":"plan-mode","category":"anthropic-skill","format":"markdown","content":"---\nname: plan-mode\ndescription: Holistic, system-aware planning before implementing non-trivial tasks. Use when the task involves new features, architectural decisions, multi-file changes, unclear requirements, or multiple valid approaches. Triggers on \"/plan\", \"plan this\", \"design an approach\", \"let's plan first\".\n---\n\nFor non-trivial implementation tasks, plan before you code. Treat every change as a system change, not an isolated patch. Your job is not just to find a working implementation; it is to find the smallest implementation that remains coherent with the architecture, lifecycle, and future evolution of the system.\n\n## Planning Doctrine\n\n- **Think broadly, implement narrowly.** Analyze the surrounding system before deciding where the change belongs.\n- **Every change has system implications.** Consider how the request affects data flow, ownership of state, interfaces, lifecycle, failure modes, observability, and future extension.\n- **Prefer authoritative, derivable designs.** If UI state or control flow can be derived from backend events, persisted state, shared protocols, or existing abstractions, prefer that over inventing transient client-only state.\n- **Choose the smallest coherent solution.** Do not default to the smallest local patch if it distorts the architecture or adds hidden follow-on complexity.\n- **Expand scope only when it simplifies the system.** Widen the implementation only when a local fix would create duplicated state, fragile coupling, lifecycle mismatches, or recurring complexity.\n- **Do not gold-plate.** Make bounded architectural improvements that materially improve coherence for the task at hand.\n\n## Plan File\n\nWrite your plan to a `PLAN.md` file in the project root. Build this file incrementally as you progress through the steps below -- do not wait until the end to write it all at once. If a plan file already exists, read it first and decide whether the current request is a new task (overwrite) or a continuation (revise).\n\n## Step 1: Explore\n\nThoroughly explore the codebase to understand the request before designing anything.\n\n- Read the relevant files and understand existing patterns, architecture, and conventions.\n- Trace the end-to-end flow, not just the local implementation point.\n- Identify the current **source of truth**, state ownership, boundaries between subsystems, and any existing invariants.\n- Identify the full lifecycle of the behavior: trigger, processing, intermediate states, completion, side effects, failure, retry, and cleanup.\n- Search for similar features and prior art in the codebase.\n- **Launch parallel explorations** when the scope is uncertain or multiple areas of the codebase are involved. Give each exploration a specific, distinct search focus (e.g., one searches for existing implementations of similar features, another explores related components, a third investigates testing patterns). Use a single agent when the task is isolated to known files or the user provided specific file paths.\n- Do not start implementing yet.\n\n## Step 2: Clarify\n\nAsk the user questions to resolve ambiguities before committing to an approach. This may cover technical implementation, desired system behavior, UI/UX, performance, edge cases, ownership of state, failure handling, or tradeoffs. You may ask multiple rounds, reading more code in between. Do not make large assumptions about user intent.\n\n## Step 3: Design\n\nBased on exploration and user input, design a concrete implementation approach:\n\n- Provide comprehensive background context including filenames, code path traces, and the surrounding system behavior discovered in Step 1.\n- Treat the request as a system modification, not just a local diff.\n- Explicitly consider both:\n  - **The most direct implementation**\n  - **The most system-coherent implementation**\n- Choose the direct implementation only if it does **not** introduce architectural distortion, duplicated state, fragile coupling, lifecycle mismatches, or hidden follow-on complexity.\n- Prefer the smallest holistic solution: think broadly about implications, then keep the implementation as narrow as possible while preserving coherence.\n- For complex tasks, consider multiple perspectives to arrive at the best approach:\n  - **New feature**: simplicity vs performance vs maintainability\n  - **Bug fix**: root cause fix vs workaround vs prevention\n  - **Refactoring**: minimal change vs clean architecture\n- For tasks that touch multiple parts of the codebase, involve large refactors, or have many edge cases, explore different approaches in parallel before converging on a recommendation.\n\nBefore finalizing the design, explicitly answer these questions:\n\n1. What part of the system is actually changing?\n2. What is the source of truth before and after this change?\n3. What new state, transitions, or invariants does this introduce?\n4. What other components, flows, or interfaces now depend on this decision?\n5. Does this create duplicated logic or state anywhere?\n6. Is there a more system-coherent place to implement this?\n7. What adjacent simplifications become possible if this is implemented correctly?\n8. What is the smallest solution that keeps the system coherent?\n\n## Step 4: Review\n\nBefore finalizing, review your design against the original request:\n\n1. Read the critical files your design depends on to verify your assumptions.\n2. Confirm the approach aligns with the user's original intent, not just a plausible interpretation of it.\n3. Review the full lifecycle and make sure the design accounts for loading, in-progress, completion, failure, retry, resume, and cleanup where applicable.\n4. Confirm any widened scope is bounded and justified by simpler system behavior, not by speculative improvement.\n5. Ask remaining clarifying questions if anything is still ambiguous.\n\n## Step 5: Present the Plan\n\nWrite the final plan to the plan file. The plan should be:\n\n- **Concise enough to scan quickly, detailed enough to execute.** Include only your recommended approach, not all alternatives.\n- **Specific about files.** List the paths of critical files to be modified and what changes in each.\n- **Explicit about system impact.** Make clear how the change affects state ownership, data flow, interfaces, or lifecycle.\n- **Verifiable.** Include how to test the changes end-to-end.\n\nStructure:\n\n```\nSummary: 1-2 sentences on the task and chosen approach\n\nContext: Key findings from exploration -- existing patterns, relevant files, constraints\n\nSystem Impact: How the change affects source of truth, data flow, lifecycle, and dependent parts of the system\n\nApproach: High-level design decision and why\n\nChanges:\n- `path/to/file.ts` - what changes and why\n- `path/to/other.ts` - what changes and why\n\nVerification:\n- How to test end-to-end\n- Relevant test commands\n- Edge cases to check\n```\n\nPresent the plan to the user and wait for approval. If the user has feedback, revise accordingly.\n\n## Step 6: Implement and Verify\n\nOnce approved:\n\n1. Implement the plan, tracking progress against each item.\n2. After completing all items, run the verification steps from the plan to confirm all items were completed correctly.\n","frontmatter":{"name":"plan-mode","description":"Holistic, system-aware planning before implementing non-trivial tasks. Use when the task involves new features, architectural decisions, multi-file changes, unclear requirements, or multiple valid approaches. Triggers on \"/plan\", \"plan this\", \"design an approach\", \"let's plan first\"."},"isInternal":false,"tokens":1429,"sizeBytes":7150},{"name":"SKILL.md","path":".agents/skills/remove-demo-limits/SKILL.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/remove-demo-limits/SKILL.md","title":"remove-demo-limits","category":"anthropic-skill","format":"markdown","content":"---\nname: remove-demo-limits\ndescription: Removes Open Harness hosted demo restrictions from a fork. Use when a maintainer wants to remove managed-template trial caps, hosted deployment gating, or \"deploy your own\" limits. Triggers on \"remove demo limits\", \"remove trial limits\", \"remove hosted restrictions\", \"open this up for my fork\", \"remove managed template restrictions\".\n---\n\nRemove the managed-template trial restrictions with the smallest coherent change.\n\n## Goal\nMake the fork behave like a normal self-hosted deployment:\n- no trial session cap\n- no trial message cap\n- no hosted-only restriction errors\n- no hosted-demo delete restrictions\n- no dead helper code or stale tests\n\n## Inspect first\nRead these files before editing:\n- `apps/web/lib/managed-template-trial.ts`\n- `apps/web/app/api/sessions/route.ts`\n- `apps/web/app/api/chat/route.ts`\n- `apps/web/app/api/sessions/[sessionId]/chats/[chatId]/messages/[messageId]/route.ts`\n- `apps/web/app/api/sessions/route.test.ts`\n- `apps/web/app/api/chat/route.test.ts`\n- `apps/web/app/api/sessions/[sessionId]/chats/[chatId]/messages/[messageId]/route.test.ts`\n\nThen search for these identifiers and strings to catch older or newer variants:\n- `isManagedTemplateTrialUser`\n- `MANAGED_TEMPLATE_TRIAL_`\n- `deploy-your-own`\n- `open-agents.dev`\n- `vercel.com`\n- `This hosted deployment includes`\n- `does not allow message deletion`\n\n## Required changes\n1. Remove the guards that block session creation, chat messages, or message deletion for managed-template users.\n2. If the auth callback still blocks non-Vercel emails on hosted deployments, remove that restriction too.\n3. Delete `apps/web/lib/managed-template-trial.ts` if it becomes unused; otherwise simplify it so no dead exports remain.\n4. Remove or update tests that assert the hosted caps, delete restrictions, or hosted-only auth gating.\n5. Do not add feature flags, env toggles, or new dependencies. Prefer deleting the restriction code outright.\n6. Keep unrelated auth, session ownership, and sandbox behavior unchanged.\n\n## Verification\nThis repo uses Bun. After the edits:\n- run `bun run ci`\n- if a failure is pre-existing, say that clearly and separate it from your changes\n\n## Output\nReport:\n- which restrictions were removed\n- which files changed\n- verification result\n","frontmatter":{"name":"remove-demo-limits","description":"Removes Open Harness hosted demo restrictions from a fork. Use when a maintainer wants to remove managed-template trial caps, hosted deployment gating, or \"deploy your own\" limits. Triggers on \"remove demo limits\", \"remove trial limits\", \"remove hosted restrictions\", \"open this up for my fork\", \"remove managed template restrictions\"."},"isInternal":false,"tokens":519,"sizeBytes":2292},{"name":"AGENTS.md","path":".agents/skills/vercel-react-best-practices/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/AGENTS.md","title":"Vercel-react-best-practices Skill","category":"anthropic-skill","format":"markdown","content":"# React Best Practices\n\n**Version 1.0.0**  \nVercel Engineering  \nJanuary 2026\n\n> **Note:**  \n> This document is mainly for agents and LLMs to follow when maintaining,  \n> generating, or refactoring React and Next.js codebases. Humans  \n> may also find it useful, but guidance here is optimized for automation  \n> and consistency by AI-assisted workflows.\n\n---\n\n## Abstract\n\nComprehensive 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.\n\n---\n\n## Table of Contents\n\n1. [Eliminating Waterfalls](#1-eliminating-waterfalls) — **CRITICAL**\n   - 1.1 [Defer Await Until Needed](#11-defer-await-until-needed)\n   - 1.2 [Dependency-Based Parallelization](#12-dependency-based-parallelization)\n   - 1.3 [Prevent Waterfall Chains in API Routes](#13-prevent-waterfall-chains-in-api-routes)\n   - 1.4 [Promise.all() for Independent Operations](#14-promiseall-for-independent-operations)\n   - 1.5 [Strategic Suspense Boundaries](#15-strategic-suspense-boundaries)\n2. [Bundle Size Optimization](#2-bundle-size-optimization) — **CRITICAL**\n   - 2.1 [Avoid Barrel File Imports](#21-avoid-barrel-file-imports)\n   - 2.2 [Conditional Module Loading](#22-conditional-module-loading)\n   - 2.3 [Defer Non-Critical Third-Party Libraries](#23-defer-non-critical-third-party-libraries)\n   - 2.4 [Dynamic Imports for Heavy Components](#24-dynamic-imports-for-heavy-components)\n   - 2.5 [Preload Based on User Intent](#25-preload-based-on-user-intent)\n3. [Server-Side Performance](#3-server-side-performance) — **HIGH**\n   - 3.1 [Authenticate Server Actions Like API Routes](#31-authenticate-server-actions-like-api-routes)\n   - 3.2 [Avoid Duplicate Serialization in RSC Props](#32-avoid-duplicate-serialization-in-rsc-props)\n   - 3.3 [Cross-Request LRU Caching](#33-cross-request-lru-caching)\n   - 3.4 [Hoist Static I/O to Module Level](#34-hoist-static-io-to-module-level)\n   - 3.5 [Minimize Serialization at RSC Boundaries](#35-minimize-serialization-at-rsc-boundaries)\n   - 3.6 [Parallel Data Fetching with Component Composition](#36-parallel-data-fetching-with-component-composition)\n   - 3.7 [Per-Request Deduplication with React.cache()](#37-per-request-deduplication-with-reactcache)\n   - 3.8 [Use after() for Non-Blocking Operations](#38-use-after-for-non-blocking-operations)\n4. [Client-Side Data Fetching](#4-client-side-data-fetching) — **MEDIUM-HIGH**\n   - 4.1 [Deduplicate Global Event Listeners](#41-deduplicate-global-event-listeners)\n   - 4.2 [Use Passive Event Listeners for Scrolling Performance](#42-use-passive-event-listeners-for-scrolling-performance)\n   - 4.3 [Use SWR for Automatic Deduplication](#43-use-swr-for-automatic-deduplication)\n   - 4.4 [Version and Minimize localStorage Data](#44-version-and-minimize-localstorage-data)\n5. [Re-render Optimization](#5-re-render-optimization) — **MEDIUM**\n   - 5.1 [Calculate Derived State During Rendering](#51-calculate-derived-state-during-rendering)\n   - 5.2 [Defer State Reads to Usage Point](#52-defer-state-reads-to-usage-point)\n   - 5.3 [Do not wrap a simple expression with a primitive result type in useMemo](#53-do-not-wrap-a-simple-expression-with-a-primitive-result-type-in-usememo)\n   - 5.4 [Extract Default Non-primitive Parameter Value from Memoized Component to Constant](#54-extract-default-non-primitive-parameter-value-from-memoized-component-to-constant)\n   - 5.5 [Extract to Memoized Components](#55-extract-to-memoized-components)\n   - 5.6 [Narrow Effect Dependencies](#56-narrow-effect-dependencies)\n   - 5.7 [Put Interaction Logic in Event Handlers](#57-put-interaction-logic-in-event-handlers)\n   - 5.8 [Subscribe to Derived State](#58-subscribe-to-derived-state)\n   - 5.9 [Use Functional setState Updates](#59-use-functional-setstate-updates)\n   - 5.10 [Use Lazy State Initialization](#510-use-lazy-state-initialization)\n   - 5.11 [Use Transitions for Non-Urgent Updates](#511-use-transitions-for-non-urgent-updates)\n   - 5.12 [Use useRef for Transient Values](#512-use-useref-for-transient-values)\n6. [Rendering Performance](#6-rendering-performance) — **MEDIUM**\n   - 6.1 [Animate SVG Wrapper Instead of SVG Element](#61-animate-svg-wrapper-instead-of-svg-element)\n   - 6.2 [CSS content-visibility for Long Lists](#62-css-content-visibility-for-long-lists)\n   - 6.3 [Hoist Static JSX Elements](#63-hoist-static-jsx-elements)\n   - 6.4 [Optimize SVG Precision](#64-optimize-svg-precision)\n   - 6.5 [Prevent Hydration Mismatch Without Flickering](#65-prevent-hydration-mismatch-without-flickering)\n   - 6.6 [Suppress Expected Hydration Mismatches](#66-suppress-expected-hydration-mismatches)\n   - 6.7 [Use Activity Component for Show/Hide](#67-use-activity-component-for-showhide)\n   - 6.8 [Use Explicit Conditional Rendering](#68-use-explicit-conditional-rendering)\n   - 6.9 [Use useTransition Over Manual Loading States](#69-use-usetransition-over-manual-loading-states)\n7. [JavaScript Performance](#7-javascript-performance) — **LOW-MEDIUM**\n   - 7.1 [Avoid Layout Thrashing](#71-avoid-layout-thrashing)\n   - 7.2 [Build Index Maps for Repeated Lookups](#72-build-index-maps-for-repeated-lookups)\n   - 7.3 [Cache Property Access in Loops](#73-cache-property-access-in-loops)\n   - 7.4 [Cache Repeated Function Calls](#74-cache-repeated-function-calls)\n   - 7.5 [Cache Storage API Calls](#75-cache-storage-api-calls)\n   - 7.6 [Combine Multiple Array Iterations](#76-combine-multiple-array-iterations)\n   - 7.7 [Early Length Check for Array Comparisons](#77-early-length-check-for-array-comparisons)\n   - 7.8 [Early Return from Functions](#78-early-return-from-functions)\n   - 7.9 [Hoist RegExp Creation](#79-hoist-regexp-creation)\n   - 7.10 [Use Loop for Min/Max Instead of Sort](#710-use-loop-for-minmax-instead-of-sort)\n   - 7.11 [Use Set/Map for O(1) Lookups](#711-use-setmap-for-o1-lookups)\n   - 7.12 [Use toSorted() Instead of sort() for Immutability](#712-use-tosorted-instead-of-sort-for-immutability)\n8. [Advanced Patterns](#8-advanced-patterns) — **LOW**\n   - 8.1 [Initialize App Once, Not Per Mount](#81-initialize-app-once-not-per-mount)\n   - 8.2 [Store Event Handlers in Refs](#82-store-event-handlers-in-refs)\n   - 8.3 [useEffectEvent for Stable Callback Refs](#83-useeffectevent-for-stable-callback-refs)\n\n---\n\n## 1. Eliminating Waterfalls\n\n**Impact: CRITICAL**\n\nWaterfalls are the #1 performance killer. Each sequential await adds full network latency. Eliminating them yields the largest gains.\n\n### 1.1 Defer Await Until Needed\n\n**Impact: HIGH (avoids blocking unused code paths)**\n\nMove `await` operations into the branches where they're actually used to avoid blocking code paths that don't need them.\n\n**Incorrect: blocks both branches**\n\n```typescript\nasync function handleRequest(userId: string, skipProcessing: boolean) {\n  const userData = await fetchUserData(userId)\n  \n  if (skipProcessing) {\n    // Returns immediately but still waited for userData\n    return { skipped: true }\n  }\n  \n  // Only this branch uses userData\n  return processUserData(userData)\n}\n```\n\n**Correct: only blocks when needed**\n\n```typescript\nasync function handleRequest(userId: string, skipProcessing: boolean) {\n  if (skipProcessing) {\n    // Returns immediately without waiting\n    return { skipped: true }\n  }\n  \n  // Fetch only when needed\n  const userData = await fetchUserData(userId)\n  return processUserData(userData)\n}\n```\n\n**Another example: early return optimization**\n\n```typescript\n// Incorrect: always fetches permissions\nasync function updateResource(resourceId: string, userId: string) {\n  const permissions = await fetchPermissions(userId)\n  const resource = await getResource(resourceId)\n  \n  if (!resource) {\n    return { error: 'Not found' }\n  }\n  \n  if (!permissions.canEdit) {\n    return { error: 'Forbidden' }\n  }\n  \n  return await updateResourceData(resource, permissions)\n}\n\n// Correct: fetches only when needed\nasync function updateResource(resourceId: string, userId: string) {\n  const resource = await getResource(resourceId)\n  \n  if (!resource) {\n    return { error: 'Not found' }\n  }\n  \n  const permissions = await fetchPermissions(userId)\n  \n  if (!permissions.canEdit) {\n    return { error: 'Forbidden' }\n  }\n  \n  return await updateResourceData(resource, permissions)\n}\n```\n\nThis optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive.\n\n### 1.2 Dependency-Based Parallelization\n\n**Impact: CRITICAL (2-10× improvement)**\n\nFor operations with partial dependencies, use `better-all` to maximize parallelism. It automatically starts each task at the earliest possible moment.\n\n**Incorrect: profile waits for config unnecessarily**\n\n```typescript\nconst [user, config] = await Promise.all([\n  fetchUser(),\n  fetchConfig()\n])\nconst profile = await fetchProfile(user.id)\n```\n\n**Correct: config and profile run in parallel**\n\n```typescript\nimport { all } from 'better-all'\n\nconst { user, config, profile } = await all({\n  async user() { return fetchUser() },\n  async config() { return fetchConfig() },\n  async profile() {\n    return fetchProfile((await this.$.user).id)\n  }\n})\n```\n\n**Alternative without extra dependencies:**\n\n```typescript\nconst userPromise = fetchUser()\nconst profilePromise = userPromise.then(user => fetchProfile(user.id))\n\nconst [user, config, profile] = await Promise.all([\n  userPromise,\n  fetchConfig(),\n  profilePromise\n])\n```\n\nWe can also create all the promises first, and do `Promise.all()` at the end.\n\nReference: [https://github.com/shuding/better-all](https://github.com/shuding/better-all)\n\n### 1.3 Prevent Waterfall Chains in API Routes\n\n**Impact: CRITICAL (2-10× improvement)**\n\nIn API routes and Server Actions, start independent operations immediately, even if you don't await them yet.\n\n**Incorrect: config waits for auth, data waits for both**\n\n```typescript\nexport async function GET(request: Request) {\n  const session = await auth()\n  const config = await fetchConfig()\n  const data = await fetchData(session.user.id)\n  return Response.json({ data, config })\n}\n```\n\n**Correct: auth and config start immediately**\n\n```typescript\nexport async function GET(request: Request) {\n  const sessionPromise = auth()\n  const configPromise = fetchConfig()\n  const session = await sessionPromise\n  const [config, data] = await Promise.all([\n    configPromise,\n    fetchData(session.user.id)\n  ])\n  return Response.json({ data, config })\n}\n```\n\nFor operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization).\n\n### 1.4 Promise.all() for Independent Operations\n\n**Impact: CRITICAL (2-10× improvement)**\n\nWhen async operations have no interdependencies, execute them concurrently using `Promise.all()`.\n\n**Incorrect: sequential execution, 3 round trips**\n\n```typescript\nconst user = await fetchUser()\nconst posts = await fetchPosts()\nconst comments = await fetchComments()\n```\n\n**Correct: parallel execution, 1 round trip**\n\n```typescript\nconst [user, posts, comments] = await Promise.all([\n  fetchUser(),\n  fetchPosts(),\n  fetchComments()\n])\n```\n\n### 1.5 Strategic Suspense Boundaries\n\n**Impact: HIGH (faster initial paint)**\n\nInstead of awaiting data in async components before returning JSX, use Suspense boundaries to show the wrapper UI faster while data loads.\n\n**Incorrect: wrapper blocked by data fetching**\n\n```tsx\nasync function Page() {\n  const data = await fetchData() // Blocks entire page\n  \n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <div>\n        <DataDisplay data={data} />\n      </div>\n      <div>Footer</div>\n    </div>\n  )\n}\n```\n\nThe entire layout waits for data even though only the middle section needs it.\n\n**Correct: wrapper shows immediately, data streams in**\n\n```tsx\nfunction Page() {\n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <div>\n        <Suspense fallback={<Skeleton />}>\n          <DataDisplay />\n        </Suspense>\n      </div>\n      <div>Footer</div>\n    </div>\n  )\n}\n\nasync function DataDisplay() {\n  const data = await fetchData() // Only blocks this component\n  return <div>{data.content}</div>\n}\n```\n\nSidebar, Header, and Footer render immediately. Only DataDisplay waits for data.\n\n**Alternative: share promise across components**\n\n```tsx\nfunction Page() {\n  // Start fetch immediately, but don't await\n  const dataPromise = fetchData()\n  \n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <Suspense fallback={<Skeleton />}>\n        <DataDisplay dataPromise={dataPromise} />\n        <DataSummary dataPromise={dataPromise} />\n      </Suspense>\n      <div>Footer</div>\n    </div>\n  )\n}\n\nfunction DataDisplay({ dataPromise }: { dataPromise: Promise<Data> }) {\n  const data = use(dataPromise) // Unwraps the promise\n  return <div>{data.content}</div>\n}\n\nfunction DataSummary({ dataPromise }: { dataPromise: Promise<Data> }) {\n  const data = use(dataPromise) // Reuses the same promise\n  return <div>{data.summary}</div>\n}\n```\n\nBoth components share the same promise, so only one fetch occurs. Layout renders immediately while both components wait together.\n\n**When NOT to use this pattern:**\n\n- Critical data needed for layout decisions (affects positioning)\n\n- SEO-critical content above the fold\n\n- Small, fast queries where suspense overhead isn't worth it\n\n- When you want to avoid layout shift (loading → content jump)\n\n**Trade-off:** Faster initial paint vs potential layout shift. Choose based on your UX priorities.\n\n---\n\n## 2. Bundle Size Optimization\n\n**Impact: CRITICAL**\n\nReducing initial bundle size improves Time to Interactive and Largest Contentful Paint.\n\n### 2.1 Avoid Barrel File Imports\n\n**Impact: CRITICAL (200-800ms import cost, slow builds)**\n\nImport directly from source files instead of barrel files to avoid loading thousands of unused modules. **Barrel files** are entry points that re-export multiple modules (e.g., `index.js` that does `export * from './module'`).\n\nPopular icon and component libraries can have **up to 10,000 re-exports** in their entry file. For many React packages, **it takes 200-800ms just to import them**, affecting both development speed and production cold starts.\n\n**Why tree-shaking doesn't help:** When a library is marked as external (not bundled), the bundler can't optimize it. If you bundle it to enable tree-shaking, builds become substantially slower analyzing the entire module graph.\n\n**Incorrect: imports entire library**\n\n```tsx\nimport { Check, X, Menu } from 'lucide-react'\n// Loads 1,583 modules, takes ~2.8s extra in dev\n// Runtime cost: 200-800ms on every cold start\n\nimport { Button, TextField } from '@mui/material'\n// Loads 2,225 modules, takes ~4.2s extra in dev\n```\n\n**Correct: imports only what you need**\n\n```tsx\nimport Check from 'lucide-react/dist/esm/icons/check'\nimport X from 'lucide-react/dist/esm/icons/x'\nimport Menu from 'lucide-react/dist/esm/icons/menu'\n// Loads only 3 modules (~2KB vs ~1MB)\n\nimport Button from '@mui/material/Button'\nimport TextField from '@mui/material/TextField'\n// Loads only what you use\n```\n\n**Alternative: Next.js 13.5+**\n\n```js\n// next.config.js - use optimizePackageImports\nmodule.exports = {\n  experimental: {\n    optimizePackageImports: ['lucide-react', '@mui/material']\n  }\n}\n\n// Then you can keep the ergonomic barrel imports:\nimport { Check, X, Menu } from 'lucide-react'\n// Automatically transformed to direct imports at build time\n```\n\nDirect imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR.\n\nLibraries commonly affected: `lucide-react`, `@mui/material`, `@mui/icons-material`, `@tabler/icons-react`, `react-icons`, `@headlessui/react`, `@radix-ui/react-*`, `lodash`, `ramda`, `date-fns`, `rxjs`, `react-use`.\n\nReference: [https://vercel.com/blog/how-we-optimized-package-imports-in-next-js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)\n\n### 2.2 Conditional Module Loading\n\n**Impact: HIGH (loads large data only when needed)**\n\nLoad large data or modules only when a feature is activated.\n\n**Example: lazy-load animation frames**\n\n```tsx\nfunction AnimationPlayer({ enabled, setEnabled }: { enabled: boolean; setEnabled: React.Dispatch<React.SetStateAction<boolean>> }) {\n  const [frames, setFrames] = useState<Frame[] | null>(null)\n\n  useEffect(() => {\n    if (enabled && !frames && typeof window !== 'undefined') {\n      import('./animation-frames.js')\n        .then(mod => setFrames(mod.frames))\n        .catch(() => setEnabled(false))\n    }\n  }, [enabled, frames, setEnabled])\n\n  if (!frames) return <Skeleton />\n  return <Canvas frames={frames} />\n}\n```\n\nThe `typeof window !== 'undefined'` check prevents bundling this module for SSR, optimizing server bundle size and build speed.\n\n### 2.3 Defer Non-Critical Third-Party Libraries\n\n**Impact: MEDIUM (loads after hydration)**\n\nAnalytics, logging, and error tracking don't block user interaction. Load them after hydration.\n\n**Incorrect: blocks initial bundle**\n\n```tsx\nimport { Analytics } from '@vercel/analytics/react'\n\nexport default function RootLayout({ children }) {\n  return (\n    <html>\n      <body>\n        {children}\n        <Analytics />\n      </body>\n    </html>\n  )\n}\n```\n\n**Correct: loads after hydration**\n\n```tsx\nimport dynamic from 'next/dynamic'\n\nconst Analytics = dynamic(\n  () => import('@vercel/analytics/react').then(m => m.Analytics),\n  { ssr: false }\n)\n\nexport default function RootLayout({ children }) {\n  return (\n    <html>\n      <body>\n        {children}\n        <Analytics />\n      </body>\n    </html>\n  )\n}\n```\n\n### 2.4 Dynamic Imports for Heavy Components\n\n**Impact: CRITICAL (directly affects TTI and LCP)**\n\nUse `next/dynamic` to lazy-load large components not needed on initial render.\n\n**Incorrect: Monaco bundles with main chunk ~300KB**\n\n```tsx\nimport { MonacoEditor } from './monaco-editor'\n\nfunction CodePanel({ code }: { code: string }) {\n  return <MonacoEditor value={code} />\n}\n```\n\n**Correct: Monaco loads on demand**\n\n```tsx\nimport dynamic from 'next/dynamic'\n\nconst MonacoEditor = dynamic(\n  () => import('./monaco-editor').then(m => m.MonacoEditor),\n  { ssr: false }\n)\n\nfunction CodePanel({ code }: { code: string }) {\n  return <MonacoEditor value={code} />\n}\n```\n\n### 2.5 Preload Based on User Intent\n\n**Impact: MEDIUM (reduces perceived latency)**\n\nPreload heavy bundles before they're needed to reduce perceived latency.\n\n**Example: preload on hover/focus**\n\n```tsx\nfunction EditorButton({ onClick }: { onClick: () => void }) {\n  const preload = () => {\n    if (typeof window !== 'undefined') {\n      void import('./monaco-editor')\n    }\n  }\n\n  return (\n    <button\n      onMouseEnter={preload}\n      onFocus={preload}\n      onClick={onClick}\n    >\n      Open Editor\n    </button>\n  )\n}\n```\n\n**Example: preload when feature flag is enabled**\n\n```tsx\nfunction FlagsProvider({ children, flags }: Props) {\n  useEffect(() => {\n    if (flags.editorEnabled && typeof window !== 'undefined') {\n      void import('./monaco-editor').then(mod => mod.init())\n    }\n  }, [flags.editorEnabled])\n\n  return <FlagsContext.Provider value={flags}>\n    {children}\n  </FlagsContext.Provider>\n}\n```\n\nThe `typeof window !== 'undefined'` check prevents bundling preloaded modules for SSR, optimizing server bundle size and build speed.\n\n---\n\n## 3. Server-Side Performance\n\n**Impact: HIGH**\n\nOptimizing server-side rendering and data fetching eliminates server-side waterfalls and reduces response times.\n\n### 3.1 Authenticate Server Actions Like API Routes\n\n**Impact: CRITICAL (prevents unauthorized access to server mutations)**\n\nServer Actions (functions with `\"use server\"`) are exposed as public endpoints, just like API routes. Always verify authentication and authorization **inside** each Server Action—do not rely solely on middleware, layout guards, or page-level checks, as Server Actions can be invoked directly.\n\nNext.js documentation explicitly states: \"Treat Server Actions with the same security considerations as public-facing API endpoints, and verify if the user is allowed to perform a mutation.\"\n\n**Incorrect: no authentication check**\n\n```typescript\n'use server'\n\nexport async function deleteUser(userId: string) {\n  // Anyone can call this! No auth check\n  await db.user.delete({ where: { id: userId } })\n  return { success: true }\n}\n```\n\n**Correct: authentication inside the action**\n\n```typescript\n'use server'\n\nimport { verifySession } from '@/lib/auth'\nimport { unauthorized } from '@/lib/errors'\n\nexport async function deleteUser(userId: string) {\n  // Always check auth inside the action\n  const session = await verifySession()\n  \n  if (!session) {\n    throw unauthorized('Must be logged in')\n  }\n  \n  // Check authorization too\n  if (session.user.role !== 'admin' && session.user.id !== userId) {\n    throw unauthorized('Cannot delete other users')\n  }\n  \n  await db.user.delete({ where: { id: userId } })\n  return { success: true }\n}\n```\n\n**With input validation:**\n\n```typescript\n'use server'\n\nimport { verifySession } from '@/lib/auth'\nimport { z } from 'zod'\n\nconst updateProfileSchema = z.object({\n  userId: z.string().uuid(),\n  name: z.string().min(1).max(100),\n  email: z.string().email()\n})\n\nexport async function updateProfile(data: unknown) {\n  // Validate input first\n  const validated = updateProfileSchema.parse(data)\n  \n  // Then authenticate\n  const session = await verifySession()\n  if (!session) {\n    throw new Error('Unauthorized')\n  }\n  \n  // Then authorize\n  if (session.user.id !== validated.userId) {\n    throw new Error('Can only update own profile')\n  }\n  \n  // Finally perform the mutation\n  await db.user.update({\n    where: { id: validated.userId },\n    data: {\n      name: validated.name,\n      email: validated.email\n    }\n  })\n  \n  return { success: true }\n}\n```\n\nReference: [https://nextjs.org/docs/app/guides/authentication](https://nextjs.org/docs/app/guides/authentication)\n\n### 3.2 Avoid Duplicate Serialization in RSC Props\n\n**Impact: LOW (reduces network payload by avoiding duplicate serialization)**\n\nRSC→client serialization deduplicates by object reference, not value. Same reference = serialized once; new reference = serialized again. Do transformations (`.toSorted()`, `.filter()`, `.map()`) in client, not server.\n\n**Incorrect: duplicates array**\n\n```tsx\n// RSC: sends 6 strings (2 arrays × 3 items)\n<ClientList usernames={usernames} usernamesOrdered={usernames.toSorted()} />\n```\n\n**Correct: sends 3 strings**\n\n```tsx\n// RSC: send once\n<ClientList usernames={usernames} />\n\n// Client: transform there\n'use client'\nconst sorted = useMemo(() => [...usernames].sort(), [usernames])\n```\n\n**Nested deduplication behavior:**\n\n```tsx\n// string[] - duplicates everything\nusernames={['a','b']} sorted={usernames.toSorted()} // sends 4 strings\n\n// object[] - duplicates array structure only\nusers={[{id:1},{id:2}]} sorted={users.toSorted()} // sends 2 arrays + 2 unique objects (not 4)\n```\n\nDeduplication works recursively. Impact varies by data type:\n\n- `string[]`, `number[]`, `boolean[]`: **HIGH impact** - array + all primitives fully duplicated\n\n- `object[]`: **LOW impact** - array duplicated, but nested objects deduplicated by reference\n\n**Operations breaking deduplication: create new references**\n\n- Arrays: `.toSorted()`, `.filter()`, `.map()`, `.slice()`, `[...arr]`\n\n- Objects: `{...obj}`, `Object.assign()`, `structuredClone()`, `JSON.parse(JSON.stringify())`\n\n**More examples:**\n\n```tsx\n// ❌ Bad\n<C users={users} active={users.filter(u => u.active)} />\n<C product={product} productName={product.name} />\n\n// ✅ Good\n<C users={users} />\n<C product={product} />\n// Do filtering/destructuring in client\n```\n\n**Exception:** Pass derived data when transformation is expensive or client doesn't need original.\n\n### 3.3 Cross-Request LRU Caching\n\n**Impact: HIGH (caches across requests)**\n\n`React.cache()` only works within one request. For data shared across sequential requests (user clicks button A then button B), use an LRU cache.\n\n**Implementation:**\n\n```typescript\nimport { LRUCache } from 'lru-cache'\n\nconst cache = new LRUCache<string, any>({\n  max: 1000,\n  ttl: 5 * 60 * 1000  // 5 minutes\n})\n\nexport async function getUser(id: string) {\n  const cached = cache.get(id)\n  if (cached) return cached\n\n  const user = await db.user.findUnique({ where: { id } })\n  cache.set(id, user)\n  return user\n}\n\n// Request 1: DB query, result cached\n// Request 2: cache hit, no DB query\n```\n\nUse when sequential user actions hit multiple endpoints needing the same data within seconds.\n\n**With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute):** LRU caching is especially effective because multiple concurrent requests can share the same function instance and cache. This means the cache persists across requests without needing external storage like Redis.\n\n**In traditional serverless:** Each invocation runs in isolation, so consider Redis for cross-process caching.\n\nReference: [https://github.com/isaacs/node-lru-cache](https://github.com/isaacs/node-lru-cache)\n\n### 3.4 Hoist Static I/O to Module Level\n\n**Impact: HIGH (avoids repeated file/network I/O per request)**\n\nWhen loading static assets (fonts, logos, images, config files) in route handlers or server functions, hoist the I/O operation to module level. Module-level code runs once when the module is first imported, not on every request. This eliminates redundant file system reads or network fetches that would otherwise run on every invocation.\n\n**Incorrect: reads font file on every request**\n\n**Correct: loads once at module initialization**\n\n**Alternative: synchronous file reads with Node.js fs**\n\n**General Node.js example: loading config or templates**\n\n**When to use this pattern:**\n\n- Loading fonts for OG image generation\n\n- Loading static logos, icons, or watermarks\n\n- Reading configuration files that don't change at runtime\n\n- Loading email templates or other static templates\n\n- Any static asset that's the same across all requests\n\n**When NOT to use this pattern:**\n\n- Assets that vary per request or user\n\n- Files that may change during runtime (use caching with TTL instead)\n\n- Large files that would consume too much memory if kept loaded\n\n- Sensitive data that shouldn't persist in memory\n\n**With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute):** Module-level caching is especially effective because multiple concurrent requests share the same function instance. The static assets stay loaded in memory across requests without cold start penalties.\n\n**In traditional serverless:** Each cold start re-executes module-level code, but subsequent warm invocations reuse the loaded assets until the instance is recycled.\n\n### 3.5 Minimize Serialization at RSC Boundaries\n\n**Impact: HIGH (reduces data transfer size)**\n\nThe React Server/Client boundary serializes all object properties into strings and embeds them in the HTML response and subsequent RSC requests. This serialized data directly impacts page weight and load time, so **size matters a lot**. Only pass fields that the client actually uses.\n\n**Incorrect: serializes all 50 fields**\n\n```tsx\nasync function Page() {\n  const user = await fetchUser()  // 50 fields\n  return <Profile user={user} />\n}\n\n'use client'\nfunction Profile({ user }: { user: User }) {\n  return <div>{user.name}</div>  // uses 1 field\n}\n```\n\n**Correct: serializes only 1 field**\n\n```tsx\nasync function Page() {\n  const user = await fetchUser()\n  return <Profile name={user.name} />\n}\n\n'use client'\nfunction Profile({ name }: { name: string }) {\n  return <div>{name}</div>\n}\n```\n\n### 3.6 Parallel Data Fetching with Component Composition\n\n**Impact: CRITICAL (eliminates server-side waterfalls)**\n\nReact Server Components execute sequentially within a tree. Restructure with composition to parallelize data fetching.\n\n**Incorrect: Sidebar waits for Page's fetch to complete**\n\n```tsx\nexport default async function Page() {\n  const header = await fetchHeader()\n  return (\n    <div>\n      <div>{header}</div>\n      <Sidebar />\n    </div>\n  )\n}\n\nasync function Sidebar() {\n  const items = await fetchSidebarItems()\n  return <nav>{items.map(renderItem)}</nav>\n}\n```\n\n**Correct: both fetch simultaneously**\n\n```tsx\nasync function Header() {\n  const data = await fetchHeader()\n  return <div>{data}</div>\n}\n\nasync function Sidebar() {\n  const items = await fetchSidebarItems()\n  return <nav>{items.map(renderItem)}</nav>\n}\n\nexport default function Page() {\n  return (\n    <div>\n      <Header />\n      <Sidebar />\n    </div>\n  )\n}\n```\n\n**Alternative with children prop:**\n\n```tsx\nasync function Header() {\n  const data = await fetchHeader()\n  return <div>{data}</div>\n}\n\nasync function Sidebar() {\n  const items = await fetchSidebarItems()\n  return <nav>{items.map(renderItem)}</nav>\n}\n\nfunction Layout({ children }: { children: ReactNode }) {\n  return (\n    <div>\n      <Header />\n      {children}\n    </div>\n  )\n}\n\nexport default function Page() {\n  return (\n    <Layout>\n      <Sidebar />\n    </Layout>\n  )\n}\n```\n\n### 3.7 Per-Request Deduplication with React.cache()\n\n**Impact: MEDIUM (deduplicates within request)**\n\nUse `React.cache()` for server-side request deduplication. Authentication and database queries benefit most.\n\n**Usage:**\n\n```typescript\nimport { cache } from 'react'\n\nexport const getCurrentUser = cache(async () => {\n  const session = await auth()\n  if (!session?.user?.id) return null\n  return await db.user.findUnique({\n    where: { id: session.user.id }\n  })\n})\n```\n\nWithin a single request, multiple calls to `getCurrentUser()` execute the query only once.\n\n**Avoid inline objects as arguments:**\n\n`React.cache()` uses shallow equality (`Object.is`) to determine cache hits. Inline objects create new references each call, preventing cache hits.\n\n**Incorrect: always cache miss**\n\n```typescript\nconst getUser = cache(async (params: { uid: number }) => {\n  return await db.user.findUnique({ where: { id: params.uid } })\n})\n\n// Each call creates new object, never hits cache\ngetUser({ uid: 1 })\ngetUser({ uid: 1 })  // Cache miss, runs query again\n```\n\n**Correct: cache hit**\n\n```typescript\nconst params = { uid: 1 }\ngetUser(params)  // Query runs\ngetUser(params)  // Cache hit (same reference)\n```\n\nIf you must pass objects, pass the same reference:\n\n**Next.js-Specific Note:**\n\nIn Next.js, the `fetch` API is automatically extended with request memoization. Requests with the same URL and options are automatically deduplicated within a single request, so you don't need `React.cache()` for `fetch` calls. However, `React.cache()` is still essential for other async tasks:\n\n- Database queries (Prisma, Drizzle, etc.)\n\n- Heavy computations\n\n- Authentication checks\n\n- File system operations\n\n- Any non-fetch async work\n\nUse `React.cache()` to deduplicate these operations across your component tree.\n\nReference: [https://react.dev/reference/react/cache](https://react.dev/reference/react/cache)\n\n### 3.8 Use after() for Non-Blocking Operations\n\n**Impact: MEDIUM (faster response times)**\n\nUse Next.js's `after()` to schedule work that should execute after a response is sent. This prevents logging, analytics, and other side effects from blocking the response.\n\n**Incorrect: blocks response**\n\n```tsx\nimport { logUserAction } from '@/app/utils'\n\nexport async function POST(request: Request) {\n  // Perform mutation\n  await updateDatabase(request)\n  \n  // Logging blocks the response\n  const userAgent = request.headers.get('user-agent') || 'unknown'\n  await logUserAction({ userAgent })\n  \n  return new Response(JSON.stringify({ status: 'success' }), {\n    status: 200,\n    headers: { 'Content-Type': 'application/json' }\n  })\n}\n```\n\n**Correct: non-blocking**\n\n```tsx\nimport { after } from 'next/server'\nimport { headers, cookies } from 'next/headers'\nimport { logUserAction } from '@/app/utils'\n\nexport async function POST(request: Request) {\n  // Perform mutation\n  await updateDatabase(request)\n  \n  // Log after response is sent\n  after(async () => {\n    const userAgent = (await headers()).get('user-agent') || 'unknown'\n    const sessionCookie = (await cookies()).get('session-id')?.value || 'anonymous'\n    \n    logUserAction({ sessionCookie, userAgent })\n  })\n  \n  return new Response(JSON.stringify({ status: 'success' }), {\n    status: 200,\n    headers: { 'Content-Type': 'application/json' }\n  })\n}\n```\n\nThe response is sent immediately while logging happens in the background.\n\n**Common use cases:**\n\n- Analytics tracking\n\n- Audit logging\n\n- Sending notifications\n\n- Cache invalidation\n\n- Cleanup tasks\n\n**Important notes:**\n\n- `after()` runs even if the response fails or redirects\n\n- Works in Server Actions, Route Handlers, and Server Components\n\nReference: [https://nextjs.org/docs/app/api-reference/functions/after](https://nextjs.org/docs/app/api-reference/functions/after)\n\n---\n\n## 4. Client-Side Data Fetching\n\n**Impact: MEDIUM-HIGH**\n\nAutomatic deduplication and efficient data fetching patterns reduce redundant network requests.\n\n### 4.1 Deduplicate Global Event Listeners\n\n**Impact: LOW (single listener for N components)**\n\nUse `useSWRSubscription()` to share global event listeners across component instances.\n\n**Incorrect: N instances = N listeners**\n\n```tsx\nfunction useKeyboardShortcut(key: string, callback: () => void) {\n  useEffect(() => {\n    const handler = (e: KeyboardEvent) => {\n      if (e.metaKey && e.key === key) {\n        callback()\n      }\n    }\n    window.addEventListener('keydown', handler)\n    return () => window.removeEventListener('keydown', handler)\n  }, [key, callback])\n}\n```\n\nWhen using the `useKeyboardShortcut` hook multiple times, each instance will register a new listener.\n\n**Correct: N instances = 1 listener**\n\n```tsx\nimport useSWRSubscription from 'swr/subscription'\n\n// Module-level Map to track callbacks per key\nconst keyCallbacks = new Map<string, Set<() => void>>()\n\nfunction useKeyboardShortcut(key: string, callback: () => void) {\n  // Register this callback in the Map\n  useEffect(() => {\n    if (!keyCallbacks.has(key)) {\n      keyCallbacks.set(key, new Set())\n    }\n    keyCallbacks.get(key)!.add(callback)\n\n    return () => {\n      const set = keyCallbacks.get(key)\n      if (set) {\n        set.delete(callback)\n        if (set.size === 0) {\n          keyCallbacks.delete(key)\n        }\n      }\n    }\n  }, [key, callback])\n\n  useSWRSubscription('global-keydown', () => {\n    const handler = (e: KeyboardEvent) => {\n      if (e.metaKey && keyCallbacks.has(e.key)) {\n        keyCallbacks.get(e.key)!.forEach(cb => cb())\n      }\n    }\n    window.addEventListener('keydown', handler)\n    return () => window.removeEventListener('keydown', handler)\n  })\n}\n\nfunction Profile() {\n  // Multiple shortcuts will share the same listener\n  useKeyboardShortcut('p', () => { /* ... */ }) \n  useKeyboardShortcut('k', () => { /* ... */ })\n  // ...\n}\n```\n\n### 4.2 Use Passive Event Listeners for Scrolling Performance\n\n**Impact: MEDIUM (eliminates scroll delay caused by event listeners)**\n\nAdd `{ passive: true }` to touch and wheel event listeners to enable immediate scrolling. Browsers normally wait for listeners to finish to check if `preventDefault()` is called, causing scroll delay.\n\n**Incorrect:**\n\n```typescript\nuseEffect(() => {\n  const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)\n  const handleWheel = (e: WheelEvent) => console.log(e.deltaY)\n  \n  document.addEventListener('touchstart', handleTouch)\n  document.addEventListener('wheel', handleWheel)\n  \n  return () => {\n    document.removeEventListener('touchstart', handleTouch)\n    document.removeEventListener('wheel', handleWheel)\n  }\n}, [])\n```\n\n**Correct:**\n\n```typescript\nuseEffect(() => {\n  const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)\n  const handleWheel = (e: WheelEvent) => console.log(e.deltaY)\n  \n  document.addEventListener('touchstart', handleTouch, { passive: true })\n  document.addEventListener('wheel', handleWheel, { passive: true })\n  \n  return () => {\n    document.removeEventListener('touchstart', handleTouch)\n    document.removeEventListener('wheel', handleWheel)\n  }\n}, [])\n```\n\n**Use passive when:** tracking/analytics, logging, any listener that doesn't call `preventDefault()`.\n\n**Don't use passive when:** implementing custom swipe gestures, custom zoom controls, or any listener that needs `preventDefault()`.\n\n### 4.3 Use SWR for Automatic Deduplication\n\n**Impact: MEDIUM-HIGH (automatic deduplication)**\n\nSWR enables request deduplication, caching, and revalidation across component instances.\n\n**Incorrect: no deduplication, each instance fetches**\n\n```tsx\nfunction UserList() {\n  const [users, setUsers] = useState([])\n  useEffect(() => {\n    fetch('/api/users')\n      .then(r => r.json())\n      .then(setUsers)\n  }, [])\n}\n```\n\n**Correct: multiple instances share one request**\n\n```tsx\nimport useSWR from 'swr'\n\nfunction UserList() {\n  const { data: users } = useSWR('/api/users', fetcher)\n}\n```\n\n**For immutable data:**\n\n```tsx\nimport { useImmutableSWR } from '@/lib/swr'\n\nfunction StaticContent() {\n  const { data } = useImmutableSWR('/api/config', fetcher)\n}\n```\n\n**For mutations:**\n\n```tsx\nimport { useSWRMutation } from 'swr/mutation'\n\nfunction UpdateButton() {\n  const { trigger } = useSWRMutation('/api/user', updateUser)\n  return <button onClick={() => trigger()}>Update</button>\n}\n```\n\nReference: [https://swr.vercel.app](https://swr.vercel.app)\n\n### 4.4 Version and Minimize localStorage Data\n\n**Impact: MEDIUM (prevents schema conflicts, reduces storage size)**\n\nAdd version prefix to keys and store only needed fields. Prevents schema conflicts and accidental storage of sensitive data.\n\n**Incorrect:**\n\n```typescript\n// No version, stores everything, no error handling\nlocalStorage.setItem('userConfig', JSON.stringify(fullUserObject))\nconst data = localStorage.getItem('userConfig')\n```\n\n**Correct:**\n\n```typescript\nconst VERSION = 'v2'\n\nfunction saveConfig(config: { theme: string; language: string }) {\n  try {\n    localStorage.setItem(`userConfig:${VERSION}`, JSON.stringify(config))\n  } catch {\n    // Throws in incognito/private browsing, quota exceeded, or disabled\n  }\n}\n\nfunction loadConfig() {\n  try {\n    const data = localStorage.getItem(`userConfig:${VERSION}`)\n    return data ? JSON.parse(data) : null\n  } catch {\n    return null\n  }\n}\n\n// Migration from v1 to v2\nfunction migrate() {\n  try {\n    const v1 = localStorage.getItem('userConfig:v1')\n    if (v1) {\n      const old = JSON.parse(v1)\n      saveConfig({ theme: old.darkMode ? 'dark' : 'light', language: old.lang })\n      localStorage.removeItem('userConfig:v1')\n    }\n  } catch {}\n}\n```\n\n**Store minimal fields from server responses:**\n\n```typescript\n// User object has 20+ fields, only store what UI needs\nfunction cachePrefs(user: FullUser) {\n  try {\n    localStorage.setItem('prefs:v1', JSON.stringify({\n      theme: user.preferences.theme,\n      notifications: user.preferences.notifications\n    }))\n  } catch {}\n}\n```\n\n**Always wrap in try-catch:** `getItem()` and `setItem()` throw in incognito/private browsing (Safari, Firefox), when quota exceeded, or when disabled.\n\n**Benefits:** Schema evolution via versioning, reduced storage size, prevents storing tokens/PII/internal flags.\n\n---\n\n## 5. Re-render Optimization\n\n**Impact: MEDIUM**\n\nReducing unnecessary re-renders minimizes wasted computation and improves UI responsiveness.\n\n### 5.1 Calculate Derived State During Rendering\n\n**Impact: MEDIUM (avoids redundant renders and state drift)**\n\nIf a value can be computed from current props/state, do not store it in state or update it in an effect. Derive it during render to avoid extra renders and state drift. Do not set state in effects solely in response to prop changes; prefer derived values or keyed resets instead.\n\n**Incorrect: redundant state and effect**\n\n```tsx\nfunction Form() {\n  const [firstName, setFirstName] = useState('First')\n  const [lastName, setLastName] = useState('Last')\n  const [fullName, setFullName] = useState('')\n\n  useEffect(() => {\n    setFullName(firstName + ' ' + lastName)\n  }, [firstName, lastName])\n\n  return <p>{fullName}</p>\n}\n```\n\n**Correct: derive during render**\n\n```tsx\nfunction Form() {\n  const [firstName, setFirstName] = useState('First')\n  const [lastName, setLastName] = useState('Last')\n  const fullName = firstName + ' ' + lastName\n\n  return <p>{fullName}</p>\n}\n```\n\nReference: [https://react.dev/learn/you-might-not-need-an-effect](https://react.dev/learn/you-might-not-need-an-effect)\n\n### 5.2 Defer State Reads to Usage Point\n\n**Impact: MEDIUM (avoids unnecessary subscriptions)**\n\nDon't subscribe to dynamic state (searchParams, localStorage) if you only read it inside callbacks.\n\n**Incorrect: subscribes to all searchParams changes**\n\n```tsx\nfunction ShareButton({ chatId }: { chatId: string }) {\n  const searchParams = useSearchParams()\n\n  const handleShare = () => {\n    const ref = searchParams.get('ref')\n    shareChat(chatId, { ref })\n  }\n\n  return <button onClick={handleShare}>Share</button>\n}\n```\n\n**Correct: reads on demand, no subscription**\n\n```tsx\nfunction ShareButton({ chatId }: { chatId: string }) {\n  const handleShare = () => {\n    const params = new URLSearchParams(window.location.search)\n    const ref = params.get('ref')\n    shareChat(chatId, { ref })\n  }\n\n  return <button onClick={handleShare}>Share</button>\n}\n```\n\n### 5.3 Do not wrap a simple expression with a primitive result type in useMemo\n\n**Impact: LOW-MEDIUM (wasted computation on every render)**\n\nWhen an expression is simple (few logical or arithmetical operators) and has a primitive result type (boolean, number, string), do not wrap it in `useMemo`.\n\nCalling `useMemo` and comparing hook dependencies may consume more resources than the expression itself.\n\n**Incorrect:**\n\n```tsx\nfunction Header({ user, notifications }: Props) {\n  const isLoading = useMemo(() => {\n    return user.isLoading || notifications.isLoading\n  }, [user.isLoading, notifications.isLoading])\n\n  if (isLoading) return <Skeleton />\n  // return some markup\n}\n```\n\n**Correct:**\n\n```tsx\nfunction Header({ user, notifications }: Props) {\n  const isLoading = user.isLoading || notifications.isLoading\n\n  if (isLoading) return <Skeleton />\n  // return some markup\n}\n```\n\n### 5.4 Extract Default Non-primitive Parameter Value from Memoized Component to Constant\n\n**Impact: MEDIUM (restores memoization by using a constant for default value)**\n\nWhen memoized component has a default value for some non-primitive optional parameter, such as an array, function, or object, calling the component without that parameter results in broken memoization. This is because new value instances are created on every rerender, and they do not pass strict equality comparison in `memo()`.\n\nTo address this issue, extract the default value into a constant.\n\n**Incorrect: `onClick` has different values on every rerender**\n\n```tsx\nconst UserAvatar = memo(function UserAvatar({ onClick = () => {} }: { onClick?: () => void }) {\n  // ...\n})\n\n// Used without optional onClick\n<UserAvatar />\n```\n\n**Correct: stable default value**\n\n```tsx\nconst NOOP = () => {};\n\nconst UserAvatar = memo(function UserAvatar({ onClick = NOOP }: { onClick?: () => void }) {\n  // ...\n})\n\n// Used without optional onClick\n<UserAvatar />\n```\n\n### 5.5 Extract to Memoized Components\n\n**Impact: MEDIUM (enables early returns)**\n\nExtract expensive work into memoized components to enable early returns before computation.\n\n**Incorrect: computes avatar even when loading**\n\n```tsx\nfunction Profile({ user, loading }: Props) {\n  const avatar = useMemo(() => {\n    const id = computeAvatarId(user)\n    return <Avatar id={id} />\n  }, [user])\n\n  if (loading) return <Skeleton />\n  return <div>{avatar}</div>\n}\n```\n\n**Correct: skips computation when loading**\n\n```tsx\nconst UserAvatar = memo(function UserAvatar({ user }: { user: User }) {\n  const id = useMemo(() => computeAvatarId(user), [user])\n  return <Avatar id={id} />\n})\n\nfunction Profile({ user, loading }: Props) {\n  if (loading) return <Skeleton />\n  return (\n    <div>\n      <UserAvatar user={user} />\n    </div>\n  )\n}\n```\n\n**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, manual memoization with `memo()` and `useMemo()` is not necessary. The compiler automatically optimizes re-renders.\n\n### 5.6 Narrow Effect Dependencies\n\n**Impact: LOW (minimizes effect re-runs)**\n\nSpecify primitive dependencies instead of objects to minimize effect re-runs.\n\n**Incorrect: re-runs on any user field change**\n\n```tsx\nuseEffect(() => {\n  console.log(user.id)\n}, [user])\n```\n\n**Correct: re-runs only when id changes**\n\n```tsx\nuseEffect(() => {\n  console.log(user.id)\n}, [user.id])\n```\n\n**For derived state, compute outside effect:**\n\n```tsx\n// Incorrect: runs on width=767, 766, 765...\nuseEffect(() => {\n  if (width < 768) {\n    enableMobileMode()\n  }\n}, [width])\n\n// Correct: runs only on boolean transition\nconst isMobile = width < 768\nuseEffect(() => {\n  if (isMobile) {\n    enableMobileMode()\n  }\n}, [isMobile])\n```\n\n### 5.7 Put Interaction Logic in Event Handlers\n\n**Impact: MEDIUM (avoids effect re-runs and duplicate side effects)**\n\nIf a side effect is triggered by a specific user action (submit, click, drag), run it in that event handler. Do not model the action as state + effect; it makes effects re-run on unrelated changes and can duplicate the action.\n\n**Incorrect: event modeled as state + effect**\n\n```tsx\nfunction Form() {\n  const [submitted, setSubmitted] = useState(false)\n  const theme = useContext(ThemeContext)\n\n  useEffect(() => {\n    if (submitted) {\n      post('/api/register')\n      showToast('Registered', theme)\n    }\n  }, [submitted, theme])\n\n  return <button onClick={() => setSubmitted(true)}>Submit</button>\n}\n```\n\n**Correct: do it in the handler**\n\n```tsx\nfunction Form() {\n  const theme = useContext(ThemeContext)\n\n  function handleSubmit() {\n    post('/api/register')\n    showToast('Registered', theme)\n  }\n\n  return <button onClick={handleSubmit}>Submit</button>\n}\n```\n\nReference: [https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler](https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler)\n\n### 5.8 Subscribe to Derived State\n\n**Impact: MEDIUM (reduces re-render frequency)**\n\nSubscribe to derived boolean state instead of continuous values to reduce re-render frequency.\n\n**Incorrect: re-renders on every pixel change**\n\n```tsx\nfunction Sidebar() {\n  const width = useWindowWidth()  // updates continuously\n  const isMobile = width < 768\n  return <nav className={isMobile ? 'mobile' : 'desktop'} />\n}\n```\n\n**Correct: re-renders only when boolean changes**\n\n```tsx\nfunction Sidebar() {\n  const isMobile = useMediaQuery('(max-width: 767px)')\n  return <nav className={isMobile ? 'mobile' : 'desktop'} />\n}\n```\n\n### 5.9 Use Functional setState Updates\n\n**Impact: MEDIUM (prevents stale closures and unnecessary callback recreations)**\n\nWhen updating state based on the current state value, use the functional update form of setState instead of directly referencing the state variable. This prevents stale closures, eliminates unnecessary dependencies, and creates stable callback references.\n\n**Incorrect: requires state as dependency**\n\n```tsx\nfunction TodoList() {\n  const [items, setItems] = useState(initialItems)\n  \n  // Callback must depend on items, recreated on every items change\n  const addItems = useCallback((newItems: Item[]) => {\n    setItems([...items, ...newItems])\n  }, [items])  // ❌ items dependency causes recreations\n  \n  // Risk of stale closure if dependency is forgotten\n  const removeItem = useCallback((id: string) => {\n    setItems(items.filter(item => item.id !== id))\n  }, [])  // ❌ Missing items dependency - will use stale items!\n  \n  return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />\n}\n```\n\nThe first callback is recreated every time `items` changes, which can cause child components to re-render unnecessarily. The second callback has a stale closure bug—it will always reference the initial `items` value.\n\n**Correct: stable callbacks, no stale closures**\n\n```tsx\nfunction TodoList() {\n  const [items, setItems] = useState(initialItems)\n  \n  // Stable callback, never recreated\n  const addItems = useCallback((newItems: Item[]) => {\n    setItems(curr => [...curr, ...newItems])\n  }, [])  // ✅ No dependencies needed\n  \n  // Always uses latest state, no stale closure risk\n  const removeItem = useCallback((id: string) => {\n    setItems(curr => curr.filter(item => item.id !== id))\n  }, [])  // ✅ Safe and stable\n  \n  return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />\n}\n```\n\n**Benefits:**\n\n1. **Stable callback references** - Callbacks don't need to be recreated when state changes\n\n2. **No stale closures** - Always operates on the latest state value\n\n3. **Fewer dependencies** - Simplifies dependency arrays and reduces memory leaks\n\n4. **Prevents bugs** - Eliminates the most common source of React closure bugs\n\n**When to use functional updates:**\n\n- Any setState that depends on the current state value\n\n- Inside useCallback/useMemo when state is needed\n\n- Event handlers that reference state\n\n- Async operations that update state\n\n**When direct updates are fine:**\n\n- Setting state to a static value: `setCount(0)`\n\n- Setting state from props/arguments only: `setName(newName)`\n\n- State doesn't depend on previous value\n\n**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler can automatically optimize some cases, but functional updates are still recommended for correctness and to prevent stale closure bugs.\n\n### 5.10 Use Lazy State Initialization\n\n**Impact: \n\n<!-- [TRUNCATED AT 50KB] -->","isInternal":false,"tokens":11965,"sizeBytes":50070},{"name":"README.md","path":".agents/skills/vercel-react-best-practices/README.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/README.md","title":"Vercel-react-best-practices Skill","category":"anthropic-skill","format":"markdown","content":"# React Best Practices\n\nA structured repository for creating and maintaining React Best Practices optimized for agents and LLMs.\n\n## Structure\n\n- `rules/` - Individual rule files (one per rule)\n  - `_sections.md` - Section metadata (titles, impacts, descriptions)\n  - `_template.md` - Template for creating new rules\n  - `area-description.md` - Individual rule files\n- `src/` - Build scripts and utilities\n- `metadata.json` - Document metadata (version, organization, abstract)\n- __`AGENTS.md`__ - Compiled output (generated)\n- __`test-cases.json`__ - Test cases for LLM evaluation (generated)\n\n## Getting Started\n\n1. Install dependencies:\n   ```bash\n   pnpm install\n   ```\n\n2. Build AGENTS.md from rules:\n   ```bash\n   pnpm build\n   ```\n\n3. Validate rule files:\n   ```bash\n   pnpm validate\n   ```\n\n4. Extract test cases:\n   ```bash\n   pnpm extract-tests\n   ```\n\n## Creating a New Rule\n\n1. Copy `rules/_template.md` to `rules/area-description.md`\n2. Choose the appropriate area prefix:\n   - `async-` for Eliminating Waterfalls (Section 1)\n   - `bundle-` for Bundle Size Optimization (Section 2)\n   - `server-` for Server-Side Performance (Section 3)\n   - `client-` for Client-Side Data Fetching (Section 4)\n   - `rerender-` for Re-render Optimization (Section 5)\n   - `rendering-` for Rendering Performance (Section 6)\n   - `js-` for JavaScript Performance (Section 7)\n   - `advanced-` for Advanced Patterns (Section 8)\n3. Fill in the frontmatter and content\n4. Ensure you have clear examples with explanations\n5. Run `pnpm build` to regenerate AGENTS.md and test-cases.json\n\n## Rule File Structure\n\nEach rule file should follow this structure:\n\n```markdown\n---\ntitle: Rule Title Here\nimpact: MEDIUM\nimpactDescription: Optional description\ntags: tag1, tag2, tag3\n---\n\n## Rule Title Here\n\nBrief explanation of the rule and why it matters.\n\n**Incorrect (description of what's wrong):**\n\n```typescript\n// Bad code example\n```\n\n**Correct (description of what's right):**\n\n```typescript\n// Good code example\n```\n\nOptional explanatory text after examples.\n\nReference: [Link](https://example.com)\n\n## File Naming Convention\n\n- Files starting with `_` are special (excluded from build)\n- Rule files: `area-description.md` (e.g., `async-parallel.md`)\n- Section is automatically inferred from filename prefix\n- Rules are sorted alphabetically by title within each section\n- IDs (e.g., 1.1, 1.2) are auto-generated during build\n\n## Impact Levels\n\n- `CRITICAL` - Highest priority, major performance gains\n- `HIGH` - Significant performance improvements\n- `MEDIUM-HIGH` - Moderate-high gains\n- `MEDIUM` - Moderate performance improvements\n- `LOW-MEDIUM` - Low-medium gains\n- `LOW` - Incremental improvements\n\n## Scripts\n\n- `pnpm build` - Compile rules into AGENTS.md\n- `pnpm validate` - Validate all rule files\n- `pnpm extract-tests` - Extract test cases for LLM evaluation\n- `pnpm dev` - Build and validate\n\n## Contributing\n\nWhen adding or modifying rules:\n\n1. Use the correct filename prefix for your section\n2. Follow the `_template.md` structure\n3. Include clear bad/good examples with explanations\n4. Add appropriate tags\n5. Run `pnpm build` to regenerate AGENTS.md and test-cases.json\n6. Rules are automatically sorted by title - no need to manage numbers!\n\n## Acknowledgments\n\nOriginally created by [@shuding](https://x.com/shuding) at [Vercel](https://vercel.com).\n","isInternal":false,"tokens":848,"sizeBytes":3360},{"name":"advanced-event-handler-refs.md","path":".agents/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Store Event Handlers in Refs\nimpact: LOW\nimpactDescription: stable subscriptions\ntags: advanced, hooks, refs, event-handlers, optimization\n---\n\n## Store Event Handlers in Refs\n\nStore callbacks in refs when used in effects that shouldn't re-subscribe on callback changes.\n\n**Incorrect (re-subscribes on every render):**\n\n```tsx\nfunction useWindowEvent(event: string, handler: (e) => void) {\n  useEffect(() => {\n    window.addEventListener(event, handler)\n    return () => window.removeEventListener(event, handler)\n  }, [event, handler])\n}\n```\n\n**Correct (stable subscription):**\n\n```tsx\nfunction useWindowEvent(event: string, handler: (e) => void) {\n  const handlerRef = useRef(handler)\n  useEffect(() => {\n    handlerRef.current = handler\n  }, [handler])\n\n  useEffect(() => {\n    const listener = (e) => handlerRef.current(e)\n    window.addEventListener(event, listener)\n    return () => window.removeEventListener(event, listener)\n  }, [event])\n}\n```\n\n**Alternative: use `useEffectEvent` if you're on latest React:**\n\n```tsx\nimport { useEffectEvent } from 'react'\n\nfunction useWindowEvent(event: string, handler: (e) => void) {\n  const onEvent = useEffectEvent(handler)\n\n  useEffect(() => {\n    window.addEventListener(event, onEvent)\n    return () => window.removeEventListener(event, onEvent)\n  }, [event])\n}\n```\n\n`useEffectEvent` provides a cleaner API for the same pattern: it creates a stable function reference that always calls the latest version of the handler.\n","frontmatter":{"title":"Store Event Handlers in Refs","impact":"LOW","impactDescription":"stable subscriptions","tags":"advanced, hooks, refs, event-handlers, optimization"},"isInternal":false,"tokens":337,"sizeBytes":1483},{"name":"advanced-init-once.md","path":".agents/skills/vercel-react-best-practices/rules/advanced-init-once.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/advanced-init-once.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Initialize App Once, Not Per Mount\nimpact: LOW-MEDIUM\nimpactDescription: avoids duplicate init in development\ntags: initialization, useEffect, app-startup, side-effects\n---\n\n## Initialize App Once, Not Per Mount\n\nDo not put app-wide initialization that must run once per app load inside `useEffect([])` of a component. Components can remount and effects will re-run. Use a module-level guard or top-level init in the entry module instead.\n\n**Incorrect (runs twice in dev, re-runs on remount):**\n\n```tsx\nfunction Comp() {\n  useEffect(() => {\n    loadFromStorage()\n    checkAuthToken()\n  }, [])\n\n  // ...\n}\n```\n\n**Correct (once per app load):**\n\n```tsx\nlet didInit = false\n\nfunction Comp() {\n  useEffect(() => {\n    if (didInit) return\n    didInit = true\n    loadFromStorage()\n    checkAuthToken()\n  }, [])\n\n  // ...\n}\n```\n\nReference: [Initializing the application](https://react.dev/learn/you-might-not-need-an-effect#initializing-the-application)\n","frontmatter":{"title":"Initialize App Once, Not Per Mount","impact":"LOW-MEDIUM","impactDescription":"avoids duplicate init in development","tags":"initialization, useEffect, app-startup, side-effects"},"isInternal":false,"tokens":230,"sizeBytes":958},{"name":"advanced-use-latest.md","path":".agents/skills/vercel-react-best-practices/rules/advanced-use-latest.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/advanced-use-latest.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: useEffectEvent for Stable Callback Refs\nimpact: LOW\nimpactDescription: prevents effect re-runs\ntags: advanced, hooks, useEffectEvent, refs, optimization\n---\n\n## useEffectEvent for Stable Callback Refs\n\nAccess latest values in callbacks without adding them to dependency arrays. Prevents effect re-runs while avoiding stale closures.\n\n**Incorrect (effect re-runs on every callback change):**\n\n```tsx\nfunction SearchInput({ onSearch }: { onSearch: (q: string) => void }) {\n  const [query, setQuery] = useState('')\n\n  useEffect(() => {\n    const timeout = setTimeout(() => onSearch(query), 300)\n    return () => clearTimeout(timeout)\n  }, [query, onSearch])\n}\n```\n\n**Correct (using React's useEffectEvent):**\n\n```tsx\nimport { useEffectEvent } from 'react';\n\nfunction SearchInput({ onSearch }: { onSearch: (q: string) => void }) {\n  const [query, setQuery] = useState('')\n  const onSearchEvent = useEffectEvent(onSearch)\n\n  useEffect(() => {\n    const timeout = setTimeout(() => onSearchEvent(query), 300)\n    return () => clearTimeout(timeout)\n  }, [query])\n}\n```\n","frontmatter":{"title":"useEffectEvent for Stable Callback Refs","impact":"LOW","impactDescription":"prevents effect re-runs","tags":"advanced, hooks, useEffectEvent, refs, optimization"},"isInternal":false,"tokens":254,"sizeBytes":1072},{"name":"async-api-routes.md","path":".agents/skills/vercel-react-best-practices/rules/async-api-routes.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/async-api-routes.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Prevent Waterfall Chains in API Routes\nimpact: CRITICAL\nimpactDescription: 2-10× improvement\ntags: api-routes, server-actions, waterfalls, parallelization\n---\n\n## Prevent Waterfall Chains in API Routes\n\nIn API routes and Server Actions, start independent operations immediately, even if you don't await them yet.\n\n**Incorrect (config waits for auth, data waits for both):**\n\n```typescript\nexport async function GET(request: Request) {\n  const session = await auth()\n  const config = await fetchConfig()\n  const data = await fetchData(session.user.id)\n  return Response.json({ data, config })\n}\n```\n\n**Correct (auth and config start immediately):**\n\n```typescript\nexport async function GET(request: Request) {\n  const sessionPromise = auth()\n  const configPromise = fetchConfig()\n  const session = await sessionPromise\n  const [config, data] = await Promise.all([\n    configPromise,\n    fetchData(session.user.id)\n  ])\n  return Response.json({ data, config })\n}\n```\n\nFor operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization).\n","frontmatter":{"title":"Prevent Waterfall Chains in API Routes","impact":"CRITICAL","impactDescription":"2-10× improvement","tags":"api-routes, server-actions, waterfalls, parallelization"},"isInternal":false,"tokens":241,"sizeBytes":1125},{"name":"async-defer-await.md","path":".agents/skills/vercel-react-best-practices/rules/async-defer-await.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/async-defer-await.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Defer Await Until Needed\nimpact: HIGH\nimpactDescription: avoids blocking unused code paths\ntags: async, await, conditional, optimization\n---\n\n## Defer Await Until Needed\n\nMove `await` operations into the branches where they're actually used to avoid blocking code paths that don't need them.\n\n**Incorrect (blocks both branches):**\n\n```typescript\nasync function handleRequest(userId: string, skipProcessing: boolean) {\n  const userData = await fetchUserData(userId)\n  \n  if (skipProcessing) {\n    // Returns immediately but still waited for userData\n    return { skipped: true }\n  }\n  \n  // Only this branch uses userData\n  return processUserData(userData)\n}\n```\n\n**Correct (only blocks when needed):**\n\n```typescript\nasync function handleRequest(userId: string, skipProcessing: boolean) {\n  if (skipProcessing) {\n    // Returns immediately without waiting\n    return { skipped: true }\n  }\n  \n  // Fetch only when needed\n  const userData = await fetchUserData(userId)\n  return processUserData(userData)\n}\n```\n\n**Another example (early return optimization):**\n\n```typescript\n// Incorrect: always fetches permissions\nasync function updateResource(resourceId: string, userId: string) {\n  const permissions = await fetchPermissions(userId)\n  const resource = await getResource(resourceId)\n  \n  if (!resource) {\n    return { error: 'Not found' }\n  }\n  \n  if (!permissions.canEdit) {\n    return { error: 'Forbidden' }\n  }\n  \n  return await updateResourceData(resource, permissions)\n}\n\n// Correct: fetches only when needed\nasync function updateResource(resourceId: string, userId: string) {\n  const resource = await getResource(resourceId)\n  \n  if (!resource) {\n    return { error: 'Not found' }\n  }\n  \n  const permissions = await fetchPermissions(userId)\n  \n  if (!permissions.canEdit) {\n    return { error: 'Forbidden' }\n  }\n  \n  return await updateResourceData(resource, permissions)\n}\n```\n\nThis optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive.\n","frontmatter":{"title":"Defer Await Until Needed","impact":"HIGH","impactDescription":"avoids blocking unused code paths","tags":"async, await, conditional, optimization"},"isInternal":false,"tokens":437,"sizeBytes":2028},{"name":"async-dependencies.md","path":".agents/skills/vercel-react-best-practices/rules/async-dependencies.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/async-dependencies.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Dependency-Based Parallelization\nimpact: CRITICAL\nimpactDescription: 2-10× improvement\ntags: async, parallelization, dependencies, better-all\n---\n\n## Dependency-Based Parallelization\n\nFor operations with partial dependencies, use `better-all` to maximize parallelism. It automatically starts each task at the earliest possible moment.\n\n**Incorrect (profile waits for config unnecessarily):**\n\n```typescript\nconst [user, config] = await Promise.all([\n  fetchUser(),\n  fetchConfig()\n])\nconst profile = await fetchProfile(user.id)\n```\n\n**Correct (config and profile run in parallel):**\n\n```typescript\nimport { all } from 'better-all'\n\nconst { user, config, profile } = await all({\n  async user() { return fetchUser() },\n  async config() { return fetchConfig() },\n  async profile() {\n    return fetchProfile((await this.$.user).id)\n  }\n})\n```\n\n**Alternative without extra dependencies:**\n\nWe can also create all the promises first, and do `Promise.all()` at the end.\n\n```typescript\nconst userPromise = fetchUser()\nconst profilePromise = userPromise.then(user => fetchProfile(user.id))\n\nconst [user, config, profile] = await Promise.all([\n  userPromise,\n  fetchConfig(),\n  profilePromise\n])\n```\n\nReference: [https://github.com/shuding/better-all](https://github.com/shuding/better-all)\n","frontmatter":{"title":"Dependency-Based Parallelization","impact":"CRITICAL","impactDescription":"2-10× improvement","tags":"async, parallelization, dependencies, better-all"},"isInternal":false,"tokens":291,"sizeBytes":1293},{"name":"async-parallel.md","path":".agents/skills/vercel-react-best-practices/rules/async-parallel.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/async-parallel.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Promise.all() for Independent Operations\nimpact: CRITICAL\nimpactDescription: 2-10× improvement\ntags: async, parallelization, promises, waterfalls\n---\n\n## Promise.all() for Independent Operations\n\nWhen async operations have no interdependencies, execute them concurrently using `Promise.all()`.\n\n**Incorrect (sequential execution, 3 round trips):**\n\n```typescript\nconst user = await fetchUser()\nconst posts = await fetchPosts()\nconst comments = await fetchComments()\n```\n\n**Correct (parallel execution, 1 round trip):**\n\n```typescript\nconst [user, posts, comments] = await Promise.all([\n  fetchUser(),\n  fetchPosts(),\n  fetchComments()\n])\n```\n","frontmatter":{"title":"Promise.all() for Independent Operations","impact":"CRITICAL","impactDescription":"2-10× improvement","tags":"async, parallelization, promises, waterfalls"},"isInternal":false,"tokens":144,"sizeBytes":654},{"name":"async-suspense-boundaries.md","path":".agents/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Strategic Suspense Boundaries\nimpact: HIGH\nimpactDescription: faster initial paint\ntags: async, suspense, streaming, layout-shift\n---\n\n## Strategic Suspense Boundaries\n\nInstead of awaiting data in async components before returning JSX, use Suspense boundaries to show the wrapper UI faster while data loads.\n\n**Incorrect (wrapper blocked by data fetching):**\n\n```tsx\nasync function Page() {\n  const data = await fetchData() // Blocks entire page\n  \n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <div>\n        <DataDisplay data={data} />\n      </div>\n      <div>Footer</div>\n    </div>\n  )\n}\n```\n\nThe entire layout waits for data even though only the middle section needs it.\n\n**Correct (wrapper shows immediately, data streams in):**\n\n```tsx\nfunction Page() {\n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <div>\n        <Suspense fallback={<Skeleton />}>\n          <DataDisplay />\n        </Suspense>\n      </div>\n      <div>Footer</div>\n    </div>\n  )\n}\n\nasync function DataDisplay() {\n  const data = await fetchData() // Only blocks this component\n  return <div>{data.content}</div>\n}\n```\n\nSidebar, Header, and Footer render immediately. Only DataDisplay waits for data.\n\n**Alternative (share promise across components):**\n\n```tsx\nfunction Page() {\n  // Start fetch immediately, but don't await\n  const dataPromise = fetchData()\n  \n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <Suspense fallback={<Skeleton />}>\n        <DataDisplay dataPromise={dataPromise} />\n        <DataSummary dataPromise={dataPromise} />\n      </Suspense>\n      <div>Footer</div>\n    </div>\n  )\n}\n\nfunction DataDisplay({ dataPromise }: { dataPromise: Promise<Data> }) {\n  const data = use(dataPromise) // Unwraps the promise\n  return <div>{data.content}</div>\n}\n\nfunction DataSummary({ dataPromise }: { dataPromise: Promise<Data> }) {\n  const data = use(dataPromise) // Reuses the same promise\n  return <div>{data.summary}</div>\n}\n```\n\nBoth components share the same promise, so only one fetch occurs. Layout renders immediately while both components wait together.\n\n**When NOT to use this pattern:**\n\n- Critical data needed for layout decisions (affects positioning)\n- SEO-critical content above the fold\n- Small, fast queries where suspense overhead isn't worth it\n- When you want to avoid layout shift (loading → content jump)\n\n**Trade-off:** Faster initial paint vs potential layout shift. Choose based on your UX priorities.\n","frontmatter":{"title":"Strategic Suspense Boundaries","impact":"HIGH","impactDescription":"faster initial paint","tags":"async, suspense, streaming, layout-shift"},"isInternal":false,"tokens":591,"sizeBytes":2510},{"name":"bundle-barrel-imports.md","path":".agents/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Avoid Barrel File Imports\nimpact: CRITICAL\nimpactDescription: 200-800ms import cost, slow builds\ntags: bundle, imports, tree-shaking, barrel-files, performance\n---\n\n## Avoid Barrel File Imports\n\nImport directly from source files instead of barrel files to avoid loading thousands of unused modules. **Barrel files** are entry points that re-export multiple modules (e.g., `index.js` that does `export * from './module'`).\n\nPopular icon and component libraries can have **up to 10,000 re-exports** in their entry file. For many React packages, **it takes 200-800ms just to import them**, affecting both development speed and production cold starts.\n\n**Why tree-shaking doesn't help:** When a library is marked as external (not bundled), the bundler can't optimize it. If you bundle it to enable tree-shaking, builds become substantially slower analyzing the entire module graph.\n\n**Incorrect (imports entire library):**\n\n```tsx\nimport { Check, X, Menu } from 'lucide-react'\n// Loads 1,583 modules, takes ~2.8s extra in dev\n// Runtime cost: 200-800ms on every cold start\n\nimport { Button, TextField } from '@mui/material'\n// Loads 2,225 modules, takes ~4.2s extra in dev\n```\n\n**Correct (imports only what you need):**\n\n```tsx\nimport Check from 'lucide-react/dist/esm/icons/check'\nimport X from 'lucide-react/dist/esm/icons/x'\nimport Menu from 'lucide-react/dist/esm/icons/menu'\n// Loads only 3 modules (~2KB vs ~1MB)\n\nimport Button from '@mui/material/Button'\nimport TextField from '@mui/material/TextField'\n// Loads only what you use\n```\n\n**Alternative (Next.js 13.5+):**\n\n```js\n// next.config.js - use optimizePackageImports\nmodule.exports = {\n  experimental: {\n    optimizePackageImports: ['lucide-react', '@mui/material']\n  }\n}\n\n// Then you can keep the ergonomic barrel imports:\nimport { Check, X, Menu } from 'lucide-react'\n// Automatically transformed to direct imports at build time\n```\n\nDirect imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR.\n\nLibraries commonly affected: `lucide-react`, `@mui/material`, `@mui/icons-material`, `@tabler/icons-react`, `react-icons`, `@headlessui/react`, `@radix-ui/react-*`, `lodash`, `ramda`, `date-fns`, `rxjs`, `react-use`.\n\nReference: [How we optimized package imports in Next.js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)\n","frontmatter":{"title":"Avoid Barrel File Imports","impact":"CRITICAL","impactDescription":"200-800ms import cost, slow builds","tags":"bundle, imports, tree-shaking, barrel-files, performance"},"isInternal":false,"tokens":595,"sizeBytes":2370},{"name":"bundle-conditional.md","path":".agents/skills/vercel-react-best-practices/rules/bundle-conditional.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/bundle-conditional.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Conditional Module Loading\nimpact: HIGH\nimpactDescription: loads large data only when needed\ntags: bundle, conditional-loading, lazy-loading\n---\n\n## Conditional Module Loading\n\nLoad large data or modules only when a feature is activated.\n\n**Example (lazy-load animation frames):**\n\n```tsx\nfunction AnimationPlayer({ enabled, setEnabled }: { enabled: boolean; setEnabled: React.Dispatch<React.SetStateAction<boolean>> }) {\n  const [frames, setFrames] = useState<Frame[] | null>(null)\n\n  useEffect(() => {\n    if (enabled && !frames && typeof window !== 'undefined') {\n      import('./animation-frames.js')\n        .then(mod => setFrames(mod.frames))\n        .catch(() => setEnabled(false))\n    }\n  }, [enabled, frames, setEnabled])\n\n  if (!frames) return <Skeleton />\n  return <Canvas frames={frames} />\n}\n```\n\nThe `typeof window !== 'undefined'` check prevents bundling this module for SSR, optimizing server bundle size and build speed.\n","frontmatter":{"title":"Conditional Module Loading","impact":"HIGH","impactDescription":"loads large data only when needed","tags":"bundle, conditional-loading, lazy-loading"},"isInternal":false,"tokens":213,"sizeBytes":949},{"name":"bundle-defer-third-party.md","path":".agents/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Defer Non-Critical Third-Party Libraries\nimpact: MEDIUM\nimpactDescription: loads after hydration\ntags: bundle, third-party, analytics, defer\n---\n\n## Defer Non-Critical Third-Party Libraries\n\nAnalytics, logging, and error tracking don't block user interaction. Load them after hydration.\n\n**Incorrect (blocks initial bundle):**\n\n```tsx\nimport { Analytics } from '@vercel/analytics/react'\n\nexport default function RootLayout({ children }) {\n  return (\n    <html>\n      <body>\n        {children}\n        <Analytics />\n      </body>\n    </html>\n  )\n}\n```\n\n**Correct (loads after hydration):**\n\n```tsx\nimport dynamic from 'next/dynamic'\n\nconst Analytics = dynamic(\n  () => import('@vercel/analytics/react').then(m => m.Analytics),\n  { ssr: false }\n)\n\nexport default function RootLayout({ children }) {\n  return (\n    <html>\n      <body>\n        {children}\n        <Analytics />\n      </body>\n    </html>\n  )\n}\n```\n","frontmatter":{"title":"Defer Non-Critical Third-Party Libraries","impact":"MEDIUM","impactDescription":"loads after hydration","tags":"bundle, third-party, analytics, defer"},"isInternal":false,"tokens":221,"sizeBytes":920},{"name":"bundle-dynamic-imports.md","path":".agents/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Dynamic Imports for Heavy Components\nimpact: CRITICAL\nimpactDescription: directly affects TTI and LCP\ntags: bundle, dynamic-import, code-splitting, next-dynamic\n---\n\n## Dynamic Imports for Heavy Components\n\nUse `next/dynamic` to lazy-load large components not needed on initial render.\n\n**Incorrect (Monaco bundles with main chunk ~300KB):**\n\n```tsx\nimport { MonacoEditor } from './monaco-editor'\n\nfunction CodePanel({ code }: { code: string }) {\n  return <MonacoEditor value={code} />\n}\n```\n\n**Correct (Monaco loads on demand):**\n\n```tsx\nimport dynamic from 'next/dynamic'\n\nconst MonacoEditor = dynamic(\n  () => import('./monaco-editor').then(m => m.MonacoEditor),\n  { ssr: false }\n)\n\nfunction CodePanel({ code }: { code: string }) {\n  return <MonacoEditor value={code} />\n}\n```\n","frontmatter":{"title":"Dynamic Imports for Heavy Components","impact":"CRITICAL","impactDescription":"directly affects TTI and LCP","tags":"bundle, dynamic-import, code-splitting, next-dynamic"},"isInternal":false,"tokens":197,"sizeBytes":791},{"name":"bundle-preload.md","path":".agents/skills/vercel-react-best-practices/rules/bundle-preload.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/bundle-preload.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Preload Based on User Intent\nimpact: MEDIUM\nimpactDescription: reduces perceived latency\ntags: bundle, preload, user-intent, hover\n---\n\n## Preload Based on User Intent\n\nPreload heavy bundles before they're needed to reduce perceived latency.\n\n**Example (preload on hover/focus):**\n\n```tsx\nfunction EditorButton({ onClick }: { onClick: () => void }) {\n  const preload = () => {\n    if (typeof window !== 'undefined') {\n      void import('./monaco-editor')\n    }\n  }\n\n  return (\n    <button\n      onMouseEnter={preload}\n      onFocus={preload}\n      onClick={onClick}\n    >\n      Open Editor\n    </button>\n  )\n}\n```\n\n**Example (preload when feature flag is enabled):**\n\n```tsx\nfunction FlagsProvider({ children, flags }: Props) {\n  useEffect(() => {\n    if (flags.editorEnabled && typeof window !== 'undefined') {\n      void import('./monaco-editor').then(mod => mod.init())\n    }\n  }, [flags.editorEnabled])\n\n  return <FlagsContext.Provider value={flags}>\n    {children}\n  </FlagsContext.Provider>\n}\n```\n\nThe `typeof window !== 'undefined'` check prevents bundling preloaded modules for SSR, optimizing server bundle size and build speed.\n","frontmatter":{"title":"Preload Based on User Intent","impact":"MEDIUM","impactDescription":"reduces perceived latency","tags":"bundle, preload, user-intent, hover"},"isInternal":false,"tokens":267,"sizeBytes":1149},{"name":"client-event-listeners.md","path":".agents/skills/vercel-react-best-practices/rules/client-event-listeners.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/client-event-listeners.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Deduplicate Global Event Listeners\nimpact: LOW\nimpactDescription: single listener for N components\ntags: client, swr, event-listeners, subscription\n---\n\n## Deduplicate Global Event Listeners\n\nUse `useSWRSubscription()` to share global event listeners across component instances.\n\n**Incorrect (N instances = N listeners):**\n\n```tsx\nfunction useKeyboardShortcut(key: string, callback: () => void) {\n  useEffect(() => {\n    const handler = (e: KeyboardEvent) => {\n      if (e.metaKey && e.key === key) {\n        callback()\n      }\n    }\n    window.addEventListener('keydown', handler)\n    return () => window.removeEventListener('keydown', handler)\n  }, [key, callback])\n}\n```\n\nWhen using the `useKeyboardShortcut` hook multiple times, each instance will register a new listener.\n\n**Correct (N instances = 1 listener):**\n\n```tsx\nimport useSWRSubscription from 'swr/subscription'\n\n// Module-level Map to track callbacks per key\nconst keyCallbacks = new Map<string, Set<() => void>>()\n\nfunction useKeyboardShortcut(key: string, callback: () => void) {\n  // Register this callback in the Map\n  useEffect(() => {\n    if (!keyCallbacks.has(key)) {\n      keyCallbacks.set(key, new Set())\n    }\n    keyCallbacks.get(key)!.add(callback)\n\n    return () => {\n      const set = keyCallbacks.get(key)\n      if (set) {\n        set.delete(callback)\n        if (set.size === 0) {\n          keyCallbacks.delete(key)\n        }\n      }\n    }\n  }, [key, callback])\n\n  useSWRSubscription('global-keydown', () => {\n    const handler = (e: KeyboardEvent) => {\n      if (e.metaKey && keyCallbacks.has(e.key)) {\n        keyCallbacks.get(e.key)!.forEach(cb => cb())\n      }\n    }\n    window.addEventListener('keydown', handler)\n    return () => window.removeEventListener('keydown', handler)\n  })\n}\n\nfunction Profile() {\n  // Multiple shortcuts will share the same listener\n  useKeyboardShortcut('p', () => { /* ... */ }) \n  useKeyboardShortcut('k', () => { /* ... */ })\n  // ...\n}\n```\n","frontmatter":{"title":"Deduplicate Global Event Listeners","impact":"LOW","impactDescription":"single listener for N components","tags":"client, swr, event-listeners, subscription"},"isInternal":false,"tokens":466,"sizeBytes":1969},{"name":"client-localstorage-schema.md","path":".agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Version and Minimize localStorage Data\nimpact: MEDIUM\nimpactDescription: prevents schema conflicts, reduces storage size\ntags: client, localStorage, storage, versioning, data-minimization\n---\n\n## Version and Minimize localStorage Data\n\nAdd version prefix to keys and store only needed fields. Prevents schema conflicts and accidental storage of sensitive data.\n\n**Incorrect:**\n\n```typescript\n// No version, stores everything, no error handling\nlocalStorage.setItem('userConfig', JSON.stringify(fullUserObject))\nconst data = localStorage.getItem('userConfig')\n```\n\n**Correct:**\n\n```typescript\nconst VERSION = 'v2'\n\nfunction saveConfig(config: { theme: string; language: string }) {\n  try {\n    localStorage.setItem(`userConfig:${VERSION}`, JSON.stringify(config))\n  } catch {\n    // Throws in incognito/private browsing, quota exceeded, or disabled\n  }\n}\n\nfunction loadConfig() {\n  try {\n    const data = localStorage.getItem(`userConfig:${VERSION}`)\n    return data ? JSON.parse(data) : null\n  } catch {\n    return null\n  }\n}\n\n// Migration from v1 to v2\nfunction migrate() {\n  try {\n    const v1 = localStorage.getItem('userConfig:v1')\n    if (v1) {\n      const old = JSON.parse(v1)\n      saveConfig({ theme: old.darkMode ? 'dark' : 'light', language: old.lang })\n      localStorage.removeItem('userConfig:v1')\n    }\n  } catch {}\n}\n```\n\n**Store minimal fields from server responses:**\n\n```typescript\n// User object has 20+ fields, only store what UI needs\nfunction cachePrefs(user: FullUser) {\n  try {\n    localStorage.setItem('prefs:v1', JSON.stringify({\n      theme: user.preferences.theme,\n      notifications: user.preferences.notifications\n    }))\n  } catch {}\n}\n```\n\n**Always wrap in try-catch:** `getItem()` and `setItem()` throw in incognito/private browsing (Safari, Firefox), when quota exceeded, or when disabled.\n\n**Benefits:** Schema evolution via versioning, reduced storage size, prevents storing tokens/PII/internal flags.\n","frontmatter":{"title":"Version and Minimize localStorage Data","impact":"MEDIUM","impactDescription":"prevents schema conflicts, reduces storage size","tags":"client, localStorage, storage, versioning, data-minimization"},"isInternal":false,"tokens":437,"sizeBytes":1950},{"name":"client-passive-event-listeners.md","path":".agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Use Passive Event Listeners for Scrolling Performance\nimpact: MEDIUM\nimpactDescription: eliminates scroll delay caused by event listeners\ntags: client, event-listeners, scrolling, performance, touch, wheel\n---\n\n## Use Passive Event Listeners for Scrolling Performance\n\nAdd `{ passive: true }` to touch and wheel event listeners to enable immediate scrolling. Browsers normally wait for listeners to finish to check if `preventDefault()` is called, causing scroll delay.\n\n**Incorrect:**\n\n```typescript\nuseEffect(() => {\n  const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)\n  const handleWheel = (e: WheelEvent) => console.log(e.deltaY)\n  \n  document.addEventListener('touchstart', handleTouch)\n  document.addEventListener('wheel', handleWheel)\n  \n  return () => {\n    document.removeEventListener('touchstart', handleTouch)\n    document.removeEventListener('wheel', handleWheel)\n  }\n}, [])\n```\n\n**Correct:**\n\n```typescript\nuseEffect(() => {\n  const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)\n  const handleWheel = (e: WheelEvent) => console.log(e.deltaY)\n  \n  document.addEventListener('touchstart', handleTouch, { passive: true })\n  document.addEventListener('wheel', handleWheel, { passive: true })\n  \n  return () => {\n    document.removeEventListener('touchstart', handleTouch)\n    document.removeEventListener('wheel', handleWheel)\n  }\n}, [])\n```\n\n**Use passive when:** tracking/analytics, logging, any listener that doesn't call `preventDefault()`.\n\n**Don't use passive when:** implementing custom swipe gestures, custom zoom controls, or any listener that needs `preventDefault()`.\n","frontmatter":{"title":"Use Passive Event Listeners for Scrolling Performance","impact":"MEDIUM","impactDescription":"eliminates scroll delay caused by event listeners","tags":"client, event-listeners, scrolling, performance, touch, wheel"},"isInternal":false,"tokens":361,"sizeBytes":1644},{"name":"client-swr-dedup.md","path":".agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Use SWR for Automatic Deduplication\nimpact: MEDIUM-HIGH\nimpactDescription: automatic deduplication\ntags: client, swr, deduplication, data-fetching\n---\n\n## Use SWR for Automatic Deduplication\n\nSWR enables request deduplication, caching, and revalidation across component instances.\n\n**Incorrect (no deduplication, each instance fetches):**\n\n```tsx\nfunction UserList() {\n  const [users, setUsers] = useState([])\n  useEffect(() => {\n    fetch('/api/users')\n      .then(r => r.json())\n      .then(setUsers)\n  }, [])\n}\n```\n\n**Correct (multiple instances share one request):**\n\n```tsx\nimport useSWR from 'swr'\n\nfunction UserList() {\n  const { data: users } = useSWR('/api/users', fetcher)\n}\n```\n\n**For immutable data:**\n\n```tsx\nimport { useImmutableSWR } from '@/lib/swr'\n\nfunction StaticContent() {\n  const { data } = useImmutableSWR('/api/config', fetcher)\n}\n```\n\n**For mutations:**\n\n```tsx\nimport { useSWRMutation } from 'swr/mutation'\n\nfunction UpdateButton() {\n  const { trigger } = useSWRMutation('/api/user', updateUser)\n  return <button onClick={() => trigger()}>Update</button>\n}\n```\n\nReference: [https://swr.vercel.app](https://swr.vercel.app)\n","frontmatter":{"title":"Use SWR for Automatic Deduplication","impact":"MEDIUM-HIGH","impactDescription":"automatic deduplication","tags":"client, swr, deduplication, data-fetching"},"isInternal":false,"tokens":308,"sizeBytes":1159},{"name":"js-batch-dom-css.md","path":".agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Avoid Layout Thrashing\nimpact: MEDIUM\nimpactDescription: prevents forced synchronous layouts and reduces performance bottlenecks\ntags: javascript, dom, css, performance, reflow, layout-thrashing\n---\n\n## Avoid Layout Thrashing\n\nAvoid interleaving style writes with layout reads. When you read a layout property (like `offsetWidth`, `getBoundingClientRect()`, or `getComputedStyle()`) between style changes, the browser is forced to trigger a synchronous reflow.\n\n**This is OK (browser batches style changes):**\n```typescript\nfunction updateElementStyles(element: HTMLElement) {\n  // Each line invalidates style, but browser batches the recalculation\n  element.style.width = '100px'\n  element.style.height = '200px'\n  element.style.backgroundColor = 'blue'\n  element.style.border = '1px solid black'\n}\n```\n\n**Incorrect (interleaved reads and writes force reflows):**\n```typescript\nfunction layoutThrashing(element: HTMLElement) {\n  element.style.width = '100px'\n  const width = element.offsetWidth  // Forces reflow\n  element.style.height = '200px'\n  const height = element.offsetHeight  // Forces another reflow\n}\n```\n\n**Correct (batch writes, then read once):**\n```typescript\nfunction updateElementStyles(element: HTMLElement) {\n  // Batch all writes together\n  element.style.width = '100px'\n  element.style.height = '200px'\n  element.style.backgroundColor = 'blue'\n  element.style.border = '1px solid black'\n  \n  // Read after all writes are done (single reflow)\n  const { width, height } = element.getBoundingClientRect()\n}\n```\n\n**Correct (batch reads, then writes):**\n```typescript\nfunction avoidThrashing(element: HTMLElement) {\n  // Read phase - all layout queries first\n  const rect1 = element.getBoundingClientRect()\n  const offsetWidth = element.offsetWidth\n  const offsetHeight = element.offsetHeight\n  \n  // Write phase - all style changes after\n  element.style.width = '100px'\n  element.style.height = '200px'\n}\n```\n\n**Better: use CSS classes**\n```css\n.highlighted-box {\n  width: 100px;\n  height: 200px;\n  background-color: blue;\n  border: 1px solid black;\n}\n```\n```typescript\nfunction updateElementStyles(element: HTMLElement) {\n  element.classList.add('highlighted-box')\n  \n  const { width, height } = element.getBoundingClientRect()\n}\n```\n\n**React example:**\n```tsx\n// Incorrect: interleaving style changes with layout queries\nfunction Box({ isHighlighted }: { isHighlighted: boolean }) {\n  const ref = useRef<HTMLDivElement>(null)\n  \n  useEffect(() => {\n    if (ref.current && isHighlighted) {\n      ref.current.style.width = '100px'\n      const width = ref.current.offsetWidth // Forces layout\n      ref.current.style.height = '200px'\n    }\n  }, [isHighlighted])\n  \n  return <div ref={ref}>Content</div>\n}\n\n// Correct: toggle class\nfunction Box({ isHighlighted }: { isHighlighted: boolean }) {\n  return (\n    <div className={isHighlighted ? 'highlighted-box' : ''}>\n      Content\n    </div>\n  )\n}\n```\n\nPrefer CSS classes over inline styles when possible. CSS files are cached by the browser, and classes provide better separation of concerns and are easier to maintain.\n\nSee [this gist](https://gist.github.com/paulirish/5d52fb081b3570c81e3a) and [CSS Triggers](https://csstriggers.com/) for more information on layout-forcing operations.\n","frontmatter":{"title":"Avoid Layout Thrashing","impact":"MEDIUM","impactDescription":"prevents forced synchronous layouts and reduces performance bottlenecks","tags":"javascript, dom, css, performance, reflow, layout-thrashing"},"isInternal":false,"tokens":757,"sizeBytes":3266},{"name":"js-cache-function-results.md","path":".agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Cache Repeated Function Calls\nimpact: MEDIUM\nimpactDescription: avoid redundant computation\ntags: javascript, cache, memoization, performance\n---\n\n## Cache Repeated Function Calls\n\nUse a module-level Map to cache function results when the same function is called repeatedly with the same inputs during render.\n\n**Incorrect (redundant computation):**\n\n```typescript\nfunction ProjectList({ projects }: { projects: Project[] }) {\n  return (\n    <div>\n      {projects.map(project => {\n        // slugify() called 100+ times for same project names\n        const slug = slugify(project.name)\n        \n        return <ProjectCard key={project.id} slug={slug} />\n      })}\n    </div>\n  )\n}\n```\n\n**Correct (cached results):**\n\n```typescript\n// Module-level cache\nconst slugifyCache = new Map<string, string>()\n\nfunction cachedSlugify(text: string): string {\n  if (slugifyCache.has(text)) {\n    return slugifyCache.get(text)!\n  }\n  const result = slugify(text)\n  slugifyCache.set(text, result)\n  return result\n}\n\nfunction ProjectList({ projects }: { projects: Project[] }) {\n  return (\n    <div>\n      {projects.map(project => {\n        // Computed only once per unique project name\n        const slug = cachedSlugify(project.name)\n        \n        return <ProjectCard key={project.id} slug={slug} />\n      })}\n    </div>\n  )\n}\n```\n\n**Simpler pattern for single-value functions:**\n\n```typescript\nlet isLoggedInCache: boolean | null = null\n\nfunction isLoggedIn(): boolean {\n  if (isLoggedInCache !== null) {\n    return isLoggedInCache\n  }\n  \n  isLoggedInCache = document.cookie.includes('auth=')\n  return isLoggedInCache\n}\n\n// Clear cache when auth changes\nfunction onAuthChange() {\n  isLoggedInCache = null\n}\n```\n\nUse a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.\n\nReference: [How we made the Vercel Dashboard twice as fast](https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast)\n","frontmatter":{"title":"Cache Repeated Function Calls","impact":"MEDIUM","impactDescription":"avoid redundant computation","tags":"javascript, cache, memoization, performance"},"isInternal":false,"tokens":448,"sizeBytes":1949},{"name":"js-cache-property-access.md","path":".agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Cache Property Access in Loops\nimpact: LOW-MEDIUM\nimpactDescription: reduces lookups\ntags: javascript, loops, optimization, caching\n---\n\n## Cache Property Access in Loops\n\nCache object property lookups in hot paths.\n\n**Incorrect (3 lookups × N iterations):**\n\n```typescript\nfor (let i = 0; i < arr.length; i++) {\n  process(obj.config.settings.value)\n}\n```\n\n**Correct (1 lookup total):**\n\n```typescript\nconst value = obj.config.settings.value\nconst len = arr.length\nfor (let i = 0; i < len; i++) {\n  process(value)\n}\n```\n","frontmatter":{"title":"Cache Property Access in Loops","impact":"LOW-MEDIUM","impactDescription":"reduces lookups","tags":"javascript, loops, optimization, caching"},"isInternal":false,"tokens":138,"sizeBytes":532},{"name":"js-cache-storage.md","path":".agents/skills/vercel-react-best-practices/rules/js-cache-storage.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/js-cache-storage.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Cache Storage API Calls\nimpact: LOW-MEDIUM\nimpactDescription: reduces expensive I/O\ntags: javascript, localStorage, storage, caching, performance\n---\n\n## Cache Storage API Calls\n\n`localStorage`, `sessionStorage`, and `document.cookie` are synchronous and expensive. Cache reads in memory.\n\n**Incorrect (reads storage on every call):**\n\n```typescript\nfunction getTheme() {\n  return localStorage.getItem('theme') ?? 'light'\n}\n// Called 10 times = 10 storage reads\n```\n\n**Correct (Map cache):**\n\n```typescript\nconst storageCache = new Map<string, string | null>()\n\nfunction getLocalStorage(key: string) {\n  if (!storageCache.has(key)) {\n    storageCache.set(key, localStorage.getItem(key))\n  }\n  return storageCache.get(key)\n}\n\nfunction setLocalStorage(key: string, value: string) {\n  localStorage.setItem(key, value)\n  storageCache.set(key, value)  // keep cache in sync\n}\n```\n\nUse a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.\n\n**Cookie caching:**\n\n```typescript\nlet cookieCache: Record<string, string> | null = null\n\nfunction getCookie(name: string) {\n  if (!cookieCache) {\n    cookieCache = Object.fromEntries(\n      document.cookie.split('; ').map(c => c.split('='))\n    )\n  }\n  return cookieCache[name]\n}\n```\n\n**Important (invalidate on external changes):**\n\nIf storage can change externally (another tab, server-set cookies), invalidate cache:\n\n```typescript\nwindow.addEventListener('storage', (e) => {\n  if (e.key) storageCache.delete(e.key)\n})\n\ndocument.addEventListener('visibilitychange', () => {\n  if (document.visibilityState === 'visible') {\n    storageCache.clear()\n  }\n})\n```\n","frontmatter":{"title":"Cache Storage API Calls","impact":"LOW-MEDIUM","impactDescription":"reduces expensive I/O","tags":"javascript, localStorage, storage, caching, performance"},"isInternal":false,"tokens":377,"sizeBytes":1651},{"name":"js-combine-iterations.md","path":".agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Combine Multiple Array Iterations\nimpact: LOW-MEDIUM\nimpactDescription: reduces iterations\ntags: javascript, arrays, loops, performance\n---\n\n## Combine Multiple Array Iterations\n\nMultiple `.filter()` or `.map()` calls iterate the array multiple times. Combine into one loop.\n\n**Incorrect (3 iterations):**\n\n```typescript\nconst admins = users.filter(u => u.isAdmin)\nconst testers = users.filter(u => u.isTester)\nconst inactive = users.filter(u => !u.isActive)\n```\n\n**Correct (1 iteration):**\n\n```typescript\nconst admins: User[] = []\nconst testers: User[] = []\nconst inactive: User[] = []\n\nfor (const user of users) {\n  if (user.isAdmin) admins.push(user)\n  if (user.isTester) testers.push(user)\n  if (!user.isActive) inactive.push(user)\n}\n```\n","frontmatter":{"title":"Combine Multiple Array Iterations","impact":"LOW-MEDIUM","impactDescription":"reduces iterations","tags":"javascript, arrays, loops, performance"},"isInternal":false,"tokens":176,"sizeBytes":753},{"name":"js-early-exit.md","path":".agents/skills/vercel-react-best-practices/rules/js-early-exit.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/js-early-exit.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Early Return from Functions\nimpact: LOW-MEDIUM\nimpactDescription: avoids unnecessary computation\ntags: javascript, functions, optimization, early-return\n---\n\n## Early Return from Functions\n\nReturn early when result is determined to skip unnecessary processing.\n\n**Incorrect (processes all items even after finding answer):**\n\n```typescript\nfunction validateUsers(users: User[]) {\n  let hasError = false\n  let errorMessage = ''\n  \n  for (const user of users) {\n    if (!user.email) {\n      hasError = true\n      errorMessage = 'Email required'\n    }\n    if (!user.name) {\n      hasError = true\n      errorMessage = 'Name required'\n    }\n    // Continues checking all users even after error found\n  }\n  \n  return hasError ? { valid: false, error: errorMessage } : { valid: true }\n}\n```\n\n**Correct (returns immediately on first error):**\n\n```typescript\nfunction validateUsers(users: User[]) {\n  for (const user of users) {\n    if (!user.email) {\n      return { valid: false, error: 'Email required' }\n    }\n    if (!user.name) {\n      return { valid: false, error: 'Name required' }\n    }\n  }\n\n  return { valid: true }\n}\n```\n","frontmatter":{"title":"Early Return from Functions","impact":"LOW-MEDIUM","impactDescription":"avoids unnecessary computation","tags":"javascript, functions, optimization, early-return"},"isInternal":false,"tokens":266,"sizeBytes":1133},{"name":"js-hoist-regexp.md","path":".agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Hoist RegExp Creation\nimpact: LOW-MEDIUM\nimpactDescription: avoids recreation\ntags: javascript, regexp, optimization, memoization\n---\n\n## Hoist RegExp Creation\n\nDon't create RegExp inside render. Hoist to module scope or memoize with `useMemo()`.\n\n**Incorrect (new RegExp every render):**\n\n```tsx\nfunction Highlighter({ text, query }: Props) {\n  const regex = new RegExp(`(${query})`, 'gi')\n  const parts = text.split(regex)\n  return <>{parts.map((part, i) => ...)}</>\n}\n```\n\n**Correct (memoize or hoist):**\n\n```tsx\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\nfunction Highlighter({ text, query }: Props) {\n  const regex = useMemo(\n    () => new RegExp(`(${escapeRegex(query)})`, 'gi'),\n    [query]\n  )\n  const parts = text.split(regex)\n  return <>{parts.map((part, i) => ...)}</>\n}\n```\n\n**Warning (global regex has mutable state):**\n\nGlobal regex (`/g`) has mutable `lastIndex` state:\n\n```typescript\nconst regex = /foo/g\nregex.test('foo')  // true, lastIndex = 3\nregex.test('foo')  // false, lastIndex = 0\n```\n","frontmatter":{"title":"Hoist RegExp Creation","impact":"LOW-MEDIUM","impactDescription":"avoids recreation","tags":"javascript, regexp, optimization, memoization"},"isInternal":false,"tokens":283,"sizeBytes":1028},{"name":"js-index-maps.md","path":".agents/skills/vercel-react-best-practices/rules/js-index-maps.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/js-index-maps.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Build Index Maps for Repeated Lookups\nimpact: LOW-MEDIUM\nimpactDescription: 1M ops to 2K ops\ntags: javascript, map, indexing, optimization, performance\n---\n\n## Build Index Maps for Repeated Lookups\n\nMultiple `.find()` calls by the same key should use a Map.\n\n**Incorrect (O(n) per lookup):**\n\n```typescript\nfunction processOrders(orders: Order[], users: User[]) {\n  return orders.map(order => ({\n    ...order,\n    user: users.find(u => u.id === order.userId)\n  }))\n}\n```\n\n**Correct (O(1) per lookup):**\n\n```typescript\nfunction processOrders(orders: Order[], users: User[]) {\n  const userById = new Map(users.map(u => [u.id, u]))\n\n  return orders.map(order => ({\n    ...order,\n    user: userById.get(order.userId)\n  }))\n}\n```\n\nBuild map once (O(n)), then all lookups are O(1).\nFor 1000 orders × 1000 users: 1M ops → 2K ops.\n","frontmatter":{"title":"Build Index Maps for Repeated Lookups","impact":"LOW-MEDIUM","impactDescription":"1M ops to 2K ops","tags":"javascript, map, indexing, optimization, performance"},"isInternal":false,"tokens":229,"sizeBytes":837},{"name":"js-length-check-first.md","path":".agents/skills/vercel-react-best-practices/rules/js-length-check-first.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/js-length-check-first.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Early Length Check for Array Comparisons\nimpact: MEDIUM-HIGH\nimpactDescription: avoids expensive operations when lengths differ\ntags: javascript, arrays, performance, optimization, comparison\n---\n\n## Early Length Check for Array Comparisons\n\nWhen comparing arrays with expensive operations (sorting, deep equality, serialization), check lengths first. If lengths differ, the arrays cannot be equal.\n\nIn real-world applications, this optimization is especially valuable when the comparison runs in hot paths (event handlers, render loops).\n\n**Incorrect (always runs expensive comparison):**\n\n```typescript\nfunction hasChanges(current: string[], original: string[]) {\n  // Always sorts and joins, even when lengths differ\n  return current.sort().join() !== original.sort().join()\n}\n```\n\nTwo O(n log n) sorts run even when `current.length` is 5 and `original.length` is 100. There is also overhead of joining the arrays and comparing the strings.\n\n**Correct (O(1) length check first):**\n\n```typescript\nfunction hasChanges(current: string[], original: string[]) {\n  // Early return if lengths differ\n  if (current.length !== original.length) {\n    return true\n  }\n  // Only sort when lengths match\n  const currentSorted = current.toSorted()\n  const originalSorted = original.toSorted()\n  for (let i = 0; i < currentSorted.length; i++) {\n    if (currentSorted[i] !== originalSorted[i]) {\n      return true\n    }\n  }\n  return false\n}\n```\n\nThis new approach is more efficient because:\n- It avoids the overhead of sorting and joining the arrays when lengths differ\n- It avoids consuming memory for the joined strings (especially important for large arrays)\n- It avoids mutating the original arrays\n- It returns early when a difference is found\n","frontmatter":{"title":"Early Length Check for Array Comparisons","impact":"MEDIUM-HIGH","impactDescription":"avoids expensive operations when lengths differ","tags":"javascript, arrays, performance, optimization, comparison"},"isInternal":false,"tokens":373,"sizeBytes":1747},{"name":"js-min-max-loop.md","path":".agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Use Loop for Min/Max Instead of Sort\nimpact: LOW\nimpactDescription: O(n) instead of O(n log n)\ntags: javascript, arrays, performance, sorting, algorithms\n---\n\n## Use Loop for Min/Max Instead of Sort\n\nFinding the smallest or largest element only requires a single pass through the array. Sorting is wasteful and slower.\n\n**Incorrect (O(n log n) - sort to find latest):**\n\n```typescript\ninterface Project {\n  id: string\n  name: string\n  updatedAt: number\n}\n\nfunction getLatestProject(projects: Project[]) {\n  const sorted = [...projects].sort((a, b) => b.updatedAt - a.updatedAt)\n  return sorted[0]\n}\n```\n\nSorts the entire array just to find the maximum value.\n\n**Incorrect (O(n log n) - sort for oldest and newest):**\n\n```typescript\nfunction getOldestAndNewest(projects: Project[]) {\n  const sorted = [...projects].sort((a, b) => a.updatedAt - b.updatedAt)\n  return { oldest: sorted[0], newest: sorted[sorted.length - 1] }\n}\n```\n\nStill sorts unnecessarily when only min/max are needed.\n\n**Correct (O(n) - single loop):**\n\n```typescript\nfunction getLatestProject(projects: Project[]) {\n  if (projects.length === 0) return null\n  \n  let latest = projects[0]\n  \n  for (let i = 1; i < projects.length; i++) {\n    if (projects[i].updatedAt > latest.updatedAt) {\n      latest = projects[i]\n    }\n  }\n  \n  return latest\n}\n\nfunction getOldestAndNewest(projects: Project[]) {\n  if (projects.length === 0) return { oldest: null, newest: null }\n  \n  let oldest = projects[0]\n  let newest = projects[0]\n  \n  for (let i = 1; i < projects.length; i++) {\n    if (projects[i].updatedAt < oldest.updatedAt) oldest = projects[i]\n    if (projects[i].updatedAt > newest.updatedAt) newest = projects[i]\n  }\n  \n  return { oldest, newest }\n}\n```\n\nSingle pass through the array, no copying, no sorting.\n\n**Alternative (Math.min/Math.max for small arrays):**\n\n```typescript\nconst numbers = [5, 2, 8, 1, 9]\nconst min = Math.min(...numbers)\nconst max = Math.max(...numbers)\n```\n\nThis works for small arrays, but can be slower or just throw an error for very large arrays due to spread operator limitations. Maximal array length is approximately 124000 in Chrome 143 and 638000 in Safari 18; exact numbers may vary - see [the fiddle](https://jsfiddle.net/qw1jabsx/4/). Use the loop approach for reliability.\n","frontmatter":{"title":"Use Loop for Min/Max Instead of Sort","impact":"LOW","impactDescription":"O(n) instead of O(n log n)","tags":"javascript, arrays, performance, sorting, algorithms"},"isInternal":false,"tokens":602,"sizeBytes":2290},{"name":"js-set-map-lookups.md","path":".agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-set-map-lookups.md -->","frontmatter":{"title":"Use Set/Map for O(1) Lookups","impact":"LOW-MEDIUM","impactDescription":"O(n) to O(1)","tags":"javascript, set, map, data-structures, performance"},"isInternal":false,"tokens":154,"sizeBytes":532},{"name":"js-tosorted-immutable.md","path":".agents/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-tosorted-immutable.md -->","frontmatter":{"title":"Use toSorted() Instead of sort() for Immutability","impact":"MEDIUM-HIGH","impactDescription":"prevents mutation bugs in React state","tags":"javascript, arrays, immutability, react, state, mutation"},"isInternal":false,"tokens":436,"sizeBytes":1782},{"name":"rendering-activity.md","path":".agents/skills/vercel-react-best-practices/rules/rendering-activity.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/rendering-activity.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-activity.md -->","frontmatter":{"title":"Use Activity Component for Show/Hide","impact":"MEDIUM","impactDescription":"preserves state/DOM","tags":"rendering, activity, visibility, state-preservation"},"isInternal":false,"tokens":131,"sizeBytes":564},{"name":"rendering-animate-svg-wrapper.md","path":".agents/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-animate-svg-wrapper.md -->","frontmatter":{"title":"Animate SVG Wrapper Instead of SVG Element","impact":"LOW","impactDescription":"enables hardware acceleration","tags":"rendering, svg, css, animation, performance"},"isInternal":false,"tokens":291,"sizeBytes":1185},{"name":"rendering-conditional-render.md","path":".agents/skills/vercel-react-best-practices/rules/rendering-conditional-render.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/rendering-conditional-render.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-conditional-render.md -->","frontmatter":{"title":"Use Explicit Conditional Rendering","impact":"LOW","impactDescription":"prevents rendering 0 or NaN","tags":"rendering, conditional, jsx, falsy-values"},"isInternal":false,"tokens":273,"sizeBytes":980},{"name":"rendering-content-visibility.md","path":".agents/skills/vercel-react-best-practices/rules/rendering-content-visibility.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/rendering-content-visibility.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-content-visibility.md -->","frontmatter":{"title":"CSS content-visibility for Long Lists","impact":"HIGH","impactDescription":"faster initial render","tags":"rendering, css, content-visibility, long-lists"},"isInternal":false,"tokens":203,"sizeBytes":815},{"name":"rendering-hoist-jsx.md","path":".agents/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-hoist-jsx.md -->","frontmatter":{"title":"Hoist Static JSX Elements","impact":"LOW","impactDescription":"avoids re-creation","tags":"rendering, jsx, static, optimization"},"isInternal":false,"tokens":244,"sizeBytes":1039},{"name":"rendering-hydration-no-flicker.md","path":".agents/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-hydration-no-flicker.md -->","frontmatter":{"title":"Prevent Hydration Mismatch Without Flickering","impact":"MEDIUM","impactDescription":"avoids visual flicker and hydration errors","tags":"rendering, ssr, hydration, localStorage, flicker"},"isInternal":false,"tokens":490,"sizeBytes":2308},{"name":"rendering-hydration-suppress-warning.md","path":".agents/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-hydration-suppress-warning.md -->","frontmatter":{"title":"Suppress Expected Hydration Mismatches","impact":"LOW-MEDIUM","impactDescription":"avoids noisy hydration warnings for known differences","tags":"rendering, hydration, ssr, nextjs"},"isInternal":false,"tokens":197,"sizeBytes":872},{"name":"rendering-svg-precision.md","path":".agents/skills/vercel-react-best-practices/rules/rendering-svg-precision.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/rendering-svg-precision.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-svg-precision.md -->","frontmatter":{"title":"Optimize SVG Precision","impact":"LOW","impactDescription":"reduces file size","tags":"rendering, svg, optimization, svgo"},"isInternal":false,"tokens":167,"sizeBytes":588},{"name":"rendering-usetransition-loading.md","path":".agents/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frendering-usetransition-loading.md -->","frontmatter":{"title":"Use useTransition Over Manual Loading States","impact":"LOW","impactDescription":"reduces re-renders and improves code clarity","tags":"rendering, transitions, useTransition, loading, state"},"isInternal":false,"tokens":457,"sizeBytes":2074},{"name":"rerender-defer-reads.md","path":".agents/skills/vercel-react-best-practices/rules/rerender-defer-reads.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/rerender-defer-reads.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-defer-reads.md -->","frontmatter":{"title":"Defer State Reads to Usage Point","impact":"MEDIUM","impactDescription":"avoids unnecessary subscriptions","tags":"rerender, searchParams, localStorage, optimization"},"isInternal":false,"tokens":231,"sizeBytes":973},{"name":"rerender-dependencies.md","path":".agents/skills/vercel-react-best-practices/rules/rerender-dependencies.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/rerender-dependencies.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-dependencies.md -->","frontmatter":{"title":"Narrow Effect Dependencies","impact":"LOW","impactDescription":"minimizes effect re-runs","tags":"rerender, useEffect, dependencies, optimization"},"isInternal":false,"tokens":214,"sizeBytes":824},{"name":"rerender-derived-state-no-effect.md","path":".agents/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-derived-state-no-effect.md -->","frontmatter":{"title":"Calculate Derived State During Rendering","impact":"MEDIUM","impactDescription":"avoids redundant renders and state drift","tags":"rerender, derived-state, useEffect, state"},"isInternal":false,"tokens":283,"sizeBytes":1201},{"name":"rerender-derived-state.md","path":".agents/skills/vercel-react-best-practices/rules/rerender-derived-state.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-derived-state.md -->","frontmatter":{"title":"Subscribe to Derived State","impact":"MEDIUM","impactDescription":"reduces re-render frequency","tags":"rerender, derived-state, media-query, optimization"},"isInternal":false,"tokens":175,"sizeBytes":728},{"name":"rerender-functional-setstate.md","path":".agents/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-functional-setstate.md -->","frontmatter":{"title":"Use Functional setState Updates","impact":"MEDIUM","impactDescription":"prevents stale closures and unnecessary callback recreations","tags":"react, hooks, useState, useCallback, callbacks, closures"},"isInternal":false,"tokens":654,"sizeBytes":2968},{"name":"rerender-lazy-state-init.md","path":".agents/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-lazy-state-init.md -->","frontmatter":{"title":"Use Lazy State Initialization","impact":"MEDIUM","impactDescription":"wasted computation on every render","tags":"react, hooks, useState, performance, initialization"},"isInternal":false,"tokens":438,"sizeBytes":2016},{"name":"rerender-memo-with-default-value.md","path":".agents/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-memo-with-default-value.md -->","frontmatter":{"title":"Extract Default Non-primitive Parameter Value from Memoized Component to Constant","impact":"MEDIUM","impactDescription":"restores memoization by using a constant for default value","tags":"rerender, memo, optimization"},"isInternal":false,"tokens":248,"sizeBytes":1173},{"name":"rerender-memo.md","path":".agents/skills/vercel-react-best-practices/rules/rerender-memo.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/rerender-memo.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-memo.md -->","frontmatter":{"title":"Extract to Memoized Components","impact":"MEDIUM","impactDescription":"enables early returns","tags":"rerender, memo, useMemo, optimization"},"isInternal":false,"tokens":278,"sizeBytes":1148},{"name":"rerender-move-effect-to-event.md","path":".agents/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-move-effect-to-event.md -->","frontmatter":{"title":"Put Interaction Logic in Event Handlers","impact":"MEDIUM","impactDescription":"avoids effect re-runs and duplicate side effects","tags":"rerender, useEffect, events, side-effects, dependencies"},"isInternal":false,"tokens":285,"sizeBytes":1268},{"name":"rerender-simple-expression-in-memo.md","path":".agents/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-simple-expression-in-memo.md -->","frontmatter":{"title":"Do not wrap a simple expression with a primitive result type in useMemo","impact":"LOW-MEDIUM","impactDescription":"wasted computation on every render","tags":"rerender, useMemo, optimization"},"isInternal":false,"tokens":217,"sizeBytes":1018},{"name":"rerender-transitions.md","path":".agents/skills/vercel-react-best-practices/rules/rerender-transitions.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/rerender-transitions.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-transitions.md -->","frontmatter":{"title":"Use Transitions for Non-Urgent Updates","impact":"MEDIUM","impactDescription":"maintains UI responsiveness","tags":"rerender, transitions, startTransition, performance"},"isInternal":false,"tokens":241,"sizeBytes":1055},{"name":"rerender-use-ref-transient-values.md","path":".agents/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Frerender-use-ref-transient-values.md -->","frontmatter":{"title":"Use useRef for Transient Values","impact":"MEDIUM","impactDescription":"avoids unnecessary re-renders on frequent updates","tags":"rerender, useref, state, performance"},"isInternal":false,"tokens":436,"sizeBytes":1742},{"name":"server-after-nonblocking.md","path":".agents/skills/vercel-react-best-practices/rules/server-after-nonblocking.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/server-after-nonblocking.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-after-nonblocking.md -->","frontmatter":{"title":"Use after() for Non-Blocking Operations","impact":"MEDIUM","impactDescription":"faster response times","tags":"server, async, logging, analytics, side-effects"},"isInternal":false,"tokens":458,"sizeBytes":2012},{"name":"server-auth-actions.md","path":".agents/skills/vercel-react-best-practices/rules/server-auth-actions.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/server-auth-actions.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-auth-actions.md -->","frontmatter":{"title":"Authenticate Server Actions Like API Routes","impact":"CRITICAL","impactDescription":"prevents unauthorized access to server mutations","tags":"server, server-actions, authentication, security, authorization"},"isInternal":false,"tokens":585,"sizeBytes":2649},{"name":"server-cache-lru.md","path":".agents/skills/vercel-react-best-practices/rules/server-cache-lru.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/server-cache-lru.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-cache-lru.md -->","frontmatter":{"title":"Cross-Request LRU Caching","impact":"HIGH","impactDescription":"caches across requests","tags":"server, cache, lru, cross-request"},"isInternal":false,"tokens":326,"sizeBytes":1353},{"name":"server-cache-react.md","path":".agents/skills/vercel-react-best-practices/rules/server-cache-react.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/server-cache-react.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-cache-react.md -->","frontmatter":{"title":"Per-Request Deduplication with React.cache()","impact":"MEDIUM","impactDescription":"deduplicates within request","tags":"server, cache, react-cache, deduplication"},"isInternal":false,"tokens":513,"sizeBytes":2228},{"name":"server-dedup-props.md","path":".agents/skills/vercel-react-best-practices/rules/server-dedup-props.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/server-dedup-props.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-dedup-props.md -->","frontmatter":{"title":"Avoid Duplicate Serialization in RSC Props","impact":"LOW","impactDescription":"reduces network payload by avoiding duplicate serialization","tags":"server, rsc, serialization, props, client-components"},"isInternal":false,"tokens":499,"sizeBytes":2060},{"name":"server-hoist-static-io.md","path":".agents/skills/vercel-react-best-practices/rules/server-hoist-static-io.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/server-hoist-static-io.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-hoist-static-io.md -->","frontmatter":{"title":"Hoist Static I/O to Module Level","impact":"HIGH","impactDescription":"avoids repeated file/network I/O per request","tags":"server, io, performance, next.js, route-handlers, og-image"},"isInternal":false,"tokens":1033,"sizeBytes":4393},{"name":"server-parallel-fetching.md","path":".agents/skills/vercel-react-best-practices/rules/server-parallel-fetching.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/server-parallel-fetching.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-parallel-fetching.md -->","frontmatter":{"title":"Parallel Data Fetching with Component Composition","impact":"CRITICAL","impactDescription":"eliminates server-side waterfalls","tags":"server, rsc, parallel-fetching, composition"},"isInternal":false,"tokens":365,"sizeBytes":1554},{"name":"server-serialization.md","path":".agents/skills/vercel-react-best-practices/rules/server-serialization.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/rules/server-serialization.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-serialization.md -->","frontmatter":{"title":"Minimize Serialization at RSC Boundaries","impact":"HIGH","impactDescription":"reduces data transfer size","tags":"server, rsc, serialization, props"},"isInternal":false,"tokens":242,"sizeBytes":996},{"name":"SKILL.md","path":".agents/skills/vercel-react-best-practices/SKILL.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/vercel-react-best-practices/SKILL.md","title":"vercel-react-best-practices","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fvercel-react-best-practices%2FSKILL.md -->","frontmatter":{"name":"vercel-react-best-practices","description":"React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.","license":"MIT","metadata":{"author":"vercel","version":"1.0.0"}},"isInternal":false,"tokens":1512,"sizeBytes":6242},{"name":"PRACTICAL-TIPS.md","path":".agents/skills/web-animation-design/PRACTICAL-TIPS.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/web-animation-design/PRACTICAL-TIPS.md","title":"Web-animation-design Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fweb-animation-design%2FPRACTICAL-TIPS.md -->","isInternal":false,"tokens":1504,"sizeBytes":6490},{"name":"SKILL.md","path":".agents/skills/web-animation-design/SKILL.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/web-animation-design/SKILL.md","title":"web-animation-design","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fweb-animation-design%2FSKILL.md -->","frontmatter":{"name":"web-animation-design","description":"Design and implement web animations that feel natural and purposeful. Use this skill proactively whenever the user asks questions about animations, motion, easing, timing, duration, springs, transitions, or animation performance. This includes questions about how to animate specific UI elements, which easing to use, animation best practices, or accessibility considerations for motion. Triggers on: easing, ease-out, ease-in, ease-in-out, cubic-bezier, bounce, spring physics, keyframes, transform, opacity, fade, slide, scale, hover effects, microinteractions, Framer Motion, React Spring, GSAP, CSS transitions, entrance/exit animations, page transitions, stagger, will-change, GPU acceleration, prefers-reduced-motion, modal/dropdown/tooltip/popover/drawer animations, gesture animations, drag interactions, button press feel, feels janky, make it smooth.","metadata":{"short-description":"Design and implement web animations that feel natural and purposeful"}},"isInternal":false,"tokens":2603,"sizeBytes":10917},{"name":"SKILL.md","path":".agents/skills/workflow/SKILL.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/.agents/skills/workflow/SKILL.md","title":"workflow","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=.agents%2Fskills%2Fworkflow%2FSKILL.md -->","frontmatter":{"name":"workflow","description":"Creates durable, resumable workflows using Vercel's Workflow DevKit. Use when building workflows that need to survive restarts, pause for external events, retry on failure, or coordinate multi-step operations over time. Triggers on mentions of \"workflow\", \"durable functions\", \"resumable\", \"workflow devkit\", or step-based orchestration.","metadata":{"author":"Vercel Inc.","version":"1.0"}},"isInternal":false,"tokens":1451,"sizeBytes":6030},{"name":"approval-system.md","path":"packages/agent/docs/approval-system.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/packages/agent/docs/approval-system.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=packages%2Fagent%2Fdocs%2Fapproval-system.md -->","isInternal":false,"tokens":424,"sizeBytes":1837},{"name":"architecture.md","path":"docs/agents/architecture.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/docs/agents/architecture.md","title":"Subagent: architecture","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=docs%2Fagents%2Farchitecture.md -->","isInternal":false,"tokens":289,"sizeBytes":1234},{"name":"code-style.md","path":"docs/agents/code-style.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/docs/agents/code-style.md","title":"Subagent: code-style","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=docs%2Fagents%2Fcode-style.md -->","isInternal":false,"tokens":1114,"sizeBytes":4833},{"name":"lessons-learned.md","path":"docs/agents/lessons-learned.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/docs/agents/lessons-learned.md","title":"Subagent: lessons-learned","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=docs%2Fagents%2Flessons-learned.md -->","isInternal":false,"tokens":4560,"sizeBytes":22143},{"name":"react-best-practices-audit.md","path":"docs/agents/react-best-practices-audit.md","rawUrl":"https://raw.githubusercontent.com/vercel-labs/open-agents/HEAD/docs/agents/react-best-practices-audit.md","title":"Subagent: react-best-practices-audit","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/vercel-labs/open-agents?path=docs%2Fagents%2Freact-best-practices-audit.md -->","isInternal":false,"tokens":3255,"sizeBytes":13154}],"systemPromptSnippet":"<yakaai_skills repo=\"vercel-labs/open-agents\">\n<!-- File: AGENTS.md (Tokens: ~1248 | Category: root-instruction) -->\n# AGENTS.md\n\nThis file provides guidance for AI coding agents working in this repository.\n\n**This is a living document.** When you make a mistake or learn something new about this codebase, add it to [Lessons Learned](docs/agents/lessons-learned.md).\n\n## Quick Links\n\n- [Architecture & Workspace Structure](docs/agents/architecture.md)\n- [Code Style & Patterns](docs/agents/code-style.md)\n- [Lessons Learned](docs/agents/lessons-learned.md)\n\n## Authentication\n\nAuthentication uses [Better Auth](https://www.better-auth.com/) with Vercel OAuth (sign-in) and GitHub OAuth (repo access). Config lives in `apps/web/lib/auth/config.ts`. Sessions are managed by better-auth's built-in session system — there is no manual JWE/encryption layer.\n\nKey env vars: `BETTER_AUTH_SECRET` (session signing), `NEXT_PUBLIC_VERCEL_APP_CLIENT_ID` + `VERCEL_APP_CLIENT_SECRET` (Vercel OAuth), plus GitHub App credentials for repo access. See `apps/web/.env.example` for the full list.\n\n## Database & Migrations\n\nSchema lives in `apps/web/lib/db/schema.ts`. Migrations are managed by Drizzle Kit.\n\n**After modifying `schema.ts`, always generate a migration:**\n\n```bash\npnpm --dir apps/web db:generate   # Creates a new .sql migration file\n```\n\nCommit the generated `.sql` file alongside the schema change. **Do not use `db:push`** except for local throwaway databases.\n\nMigrations run automatically during `pnpm build` (via `lib/db/migrate.ts`), so every Vercel deploy — both preview and production — applies pending migrations to its own database.\n\n### Environment isolation\n\nNeon database branching is enabled in the Vercel project settings. Every preview deployment automatically gets its own isolated database branch forked from production. This means preview deployments never read or write production data. Production deployments use the main Neon database.\n\n## Commands\n\n```bash\n# Development\npnpm web            # Run web app\n\n# Quality checks (REQUIRED after making any changes)\npnpm run ci                             # Required: run format check, lint, typecheck, and tests\nturbo typecheck                            # Type check all packages\n\n# Linting and formatting (Ultracite - oxlint + oxfmt, run from root)\npnpm check                              # Lint and format check all files\npnpm fix                                # Lint fix and format all files\n\n# Filter by package (use --filter)\nturbo typecheck --filter=web # Type check web app only\n\n# Testing\nbun test                                              # Run all tests\nbun test path/to/file.test.ts                         # Run single test file\nbun test --watch                                      # Watch mode\npnpm test:verbose                                  # Run tests with JUnit reporter streamed to stdout (useful in non-interactive shells)\npnpm test:verbose path/to/file.test.ts             # Same verbose output for a single test file\n```\n\n**CI/script execution rules:**\n\n- Run project checks through package scripts (for example `pnpm run ci`, `pnpm --dir apps/web db:check`).\n- Prefer `pnpm <script>` over invoking tool binaries directly (`pnpm exec`, `tsc`, `eslint`, etc.) so local runs match CI behavior.\n\n## Git Commands\n\n- **Branch sync preference:** When bringing in `origin/main`, prefer a normal merge (`git fetch origin main` then `git merge origin/main`) instead of rebasing, unless explicitly requested otherwise.\n\n**Quote paths with special characters**: File paths containing brackets (like Next.js dynamic routes `[id]`, `[slug]`) are interpreted as glob patterns by zsh. Always quote these paths in git commands:\n\n```bash\n# Wrong - zsh interprets [id] as a glob pattern\ngit add apps/web/app/tasks/[id]/page.tsx\n# Error: no matches found: apps/web/app/tasks/[id]/page.tsx\n\n# Correct - quote the path\ngit add \"apps/web/app/tasks/[id]/page.tsx\"\n```\n\n## Architecture (Summary)\n\n```\nWeb -> Agent (packages/agent) -> Sandbox (packages/sandbox)\n```\n\nSee [Architecture & Workspace Structure](docs/agents/architecture.md) for details.\n\n## File Organization & Separation of Concerns\n\n- Do **not** append new functionality to the bottom of an existing file by default.\n- Before adding code, decide whether the behavior is a separate concern that should live in its own file.\n- Prefer creating a new colocated file for distinct concerns (components, hooks, utilities, schemas, data-access helpers, etc.).\n- If a file is already large or handling multiple responsibilities, extract the new logic (and related helpers/types) into focused modules and import them.\n- For large page/view/client components, default to adding new feature behavior in colocated hooks and colocated child components instead of growing the main file.\n- If a change introduces a distinct cluster of state, effects, handlers, API calls, or derived UI labels for one feature, treat that as a strong signal to extract it.\n- Keep each file focused on one primary responsibility; avoid mixing unrelated UI, business logic, and data-access code in the same file.\n\n## Code Style (Summary)\n\n- **pnpm exclusively for dependency management**; use Node 24 for utility scripts and Bun for tests\n- **Files**: kebab-case, **Types**: PascalCase, **Functions**: camelCase\n- **Never use `any`** -- use `unknown` and narrow with type guards\n- **No `.js` extensions** in imports\n- **Ultracite** (oxlint + oxfmt) for linting and formatting (double quotes, 2-space indent)\n- **Zod** schemas for validation, derive types with `z.infer`\n\nSee [Code Style & Patterns](docs/agents/code-style.md) for full conventions, tool implementation patterns, and dependency patterns.\n\n<!-- File: CLAUDE.md (Tokens: ~3 | Category: claude-rule) -->\nAGENTS.md\n\n<!-- File: .agents/skills/agent-browser/references/authentication.md (Tokens: ~1040 | Category: anthropic-skill) -->\n# Authentication Patterns\n\nPatterns for handling login flows, session persistence, and authenticated browsing.\n\n## Basic Login Flow\n\n```bash\n# Navigate to login page\nagent-browser open https://app.example.com/login\nagent-browser wait --load networkidle\n\n# Get form elements\nagent-browser snapshot -i\n# Output: @e1 [input type=\"email\"], @e2 [input type=\"password\"], @e3 [button] \"Sign In\"\n\n# Fill credentials\nagent-browser fill @e1 \"user@example.com\"\nagent-browser fill @e2 \"password123\"\n\n# Submit\nagent-browser click @e3\nagent-browser wait --load networkidle\n\n# Verify login succeeded\nagent-browser get url  # Should be dashboard, not login\n```\n\n## Saving Authentication State\n\nAfter logging in, save state for reuse:\n\n```bash\n# Login first (see above)\nagent-browser open https://app.example.com/login\nagent-browser snapshot -i\nagent-browser fill @e1 \"user@example.com\"\nagent-browser fill @e2 \"password123\"\nagent-browser click @e3\nagent-browser wait --url \"**/dashboard\"\n\n# Save authenticated state\nagent-browser state save ./auth-state.json\n```\n\n## Restoring Authentication\n\nSkip login by loading saved state:\n\n```bash\n# Load saved auth state\nagent-browser state load ./auth-state.json\n\n# Navigate directly to protected page\nagent-browser open https://app.example.com/dashboard\n\n# Verify authenticated\nagent-browser snapshot -i\n```\n\n## OAuth / SSO Flows\n\nFor OAuth redirects:\n\n```bash\n# Start OAuth flow\nagent-browser open https://app.example.com/auth/google\n\n# Handle redirects automatically\nagent-browser wait --url \"**/accounts.google.com**\"\nagent-browser snapshot -i\n\n# Fill Google credentials\nagent-browser fill @e1 \"user@gmail.com\"\nagent-browser click @e2  # Next button\nagent-browser wait 2000\nagent-browser snapshot -i\nagent-browser fill @e3 \"password\"\nagent-browser click @e4  # Sign in\n\n# Wait for redirect back\nagent-browser wait --url \"**/app.example.com**\"\nagent-browser state save ./oauth-state.json\n```\n\n## Two-Factor Authentication\n\nHandle 2FA with manual intervention:\n\n```bash\n# Login with credentials\nagent-browser open https://app.example.com/login --headed  # Show browser\nagent-browser snapshot -i\nagent-browser fill @e1 \"user@example.com\"\nagent-browser fill @e2 \"password123\"\nagent-browser click @e3\n\n# Wait for user to complete 2FA manually\necho \"Complete 2FA in the browser window...\"\nagent-browser wait --url \"**/dashboard\" --timeout 120000\n\n# Save state after 2FA\nagent-browser state save ./2fa-state.json\n```\n\n## HTTP Basic Auth\n\nFor sites using HTTP Basic Authentication:\n\n```bash\n# Set credentials before navigation\nagent-browser set credentials username password\n\n# Navigate to protected resource\nagent-browser open https://protected.example.com/api\n```\n\n## Cookie-Based Auth\n\nManually set authentication cookies:\n\n```bash\n# Set auth cookie\nagent-browser cookies set session_token \"abc123xyz\"\n\n# Navigate to protected page\nagent-browser open https://app.example.com/dashboard\n```\n\n## Token Refresh Handling\n\nFor sessions with expiring tokens:\n\n```bash\n#!/bin/bash\n# Wrapper that handles token refresh\n\nSTATE_FILE=\"./auth-state.json\"\n\n# Try loading existing state\nif [[ -f \"$STATE_FILE\" ]]; then\n    agent-browser state load \"$STATE_FILE\"\n    agent-browser open https://app.example.com/dashboard\n\n    # Check if session is still valid\n    URL=$(agent-browser get url)\n    if [[ \"$URL\" == *\"/login\"* ]]; then\n        echo \"Session expired, re-authenticating...\"\n        # Perform fresh login\n        agent-browser snapshot -i\n        agent-browser fill @e1 \"$USERNAME\"\n        agent-browser fill @e2 \"$PASSWORD\"\n        agent-browser click @e3\n        agent-browser wait --url \"**/dashboard\"\n        agent-browser state save \"$STATE_FILE\"\n    fi\nelse\n    # First-time login\n    agent-browser open https://app.example.com/login\n    # ... login flow ...\nfi\n```\n\n## Security Best Practices\n\n1. **Never commit state files** - They contain session tokens\n   ```bash\n   echo \"*.auth-state.json\" >> .gitignore\n   ```\n\n2. **Use environment variables for credentials**\n   ```bash\n   agent-browser fill @e1 \"$APP_USERNAME\"\n   agent-browser fill @e2 \"$APP_PASSWORD\"\n   ```\n\n3. **Clean up after automation**\n   ```bash\n   agent-browser cookies clear\n   rm -f ./auth-state.json\n   ```\n\n4. **Use short-lived sessions for CI/CD**\n   ```bash\n   # Don't persist state in CI\n   agent-browser open https://app.example.com/login\n   # ... login and perform actions ...\n   agent-browser close  # Session ends, nothing persisted\n   ```\n\n<!-- File: .agents/skills/agent-browser/references/proxy-support.md (Tokens: ~1055 | Category: anthropic-skill) -->\n# Proxy Support\n\nConfigure proxy servers for browser automation, useful for geo-testing, rate limiting avoidance, and corporate environments.\n\n## Basic Proxy Configuration\n\nSet proxy via environment variable before starting:\n\n```bash\n# HTTP proxy\nexport HTTP_PROXY=\"http://proxy.example.com:8080\"\nagent-browser open https://example.com\n\n# HTTPS proxy\nexport HTTPS_PROXY=\"https://proxy.example.com:8080\"\nagent-browser open https://example.com\n\n# Both\nexport HTTP_PROXY=\"http://proxy.example.com:8080\"\nexport HTTPS_PROXY=\"http://proxy.example.com:8080\"\nagent-browser open https://example.com\n```\n\n## Authenticated Proxy\n\nFor proxies requiring authentication:\n\n```bash\n# Include credentials in URL\nexport HTTP_PROXY=\"http://username:password@proxy.example.com:8080\"\nagent-browser open https://example.com\n```\n\n## SOCKS Proxy\n\n```bash\n# SOCKS5 proxy\nexport ALL_PROXY=\"socks5://proxy.example.com:1080\"\nagent-browser open https://example.com\n\n# SOCKS5 with auth\nexport ALL_PROXY=\"socks5://user:pass@proxy.example.com:1080\"\nagent-browser open https://example.com\n```\n\n## Proxy Bypass\n\nSkip proxy for specific domains:\n\n```bash\n# Bypass proxy for local addresses\nexport NO_PROXY=\"localhost,127.0.0.1,.internal.company.com\"\nagent-browser open https://internal.company.com  # Direct connection\nagent-browser open https://external.com          # Via proxy\n```\n\n## Common Use Cases\n\n### Geo-Location Testing\n\n```bash\n#!/bin/bash\n# Test site from different regions using geo-located proxies\n\nPROXIES=(\n    \"http://us-proxy.example.com:8080\"\n    \"http://eu-proxy.example.com:8080\"\n    \"http://asia-proxy.example.com:8080\"\n)\n\nfor proxy in \"${PROXIES[@]}\"; do\n    export HTTP_PROXY=\"$proxy\"\n    export HTTPS_PROXY=\"$proxy\"\n\n    region=$(echo \"$proxy\" | grep -oP '^\\w+-\\w+')\n    echo \"Testing from: $region\"\n\n    agent-browser --session \"$region\" open https://example.com\n    agent-browser --session \"$region\" screenshot \"./screenshots/$region.png\"\n    agent-browser --session \"$region\" close\ndone\n```\n\n### Rotating Proxies for Scraping\n\n```bash\n#!/bin/bash\n# Rotate through proxy list to avoid rate limiting\n\nPROXY_LIST=(\n    \"http://proxy1.example.com:8080\"\n    \"http://proxy2.example.com:8080\"\n    \"http://proxy3.example.com:8080\"\n)\n\nURLS=(\n    \"https://site.com/page1\"\n    \"https://site.com/page2\"\n    \"https://site.com/page3\"\n)\n\nfor i in \"${!URLS[@]}\"; do\n    proxy_index=$((i % ${#PROXY_LIST[@]}))\n    export HTTP_PROXY=\"${PROXY_LIST[$proxy_index]}\"\n    export HTTPS_PROXY=\"${PROXY_LIST[$proxy_index]}\"\n\n    agent-browser open \"${URLS[$i]}\"\n    agent-browser get text body > \"output-$i.txt\"\n    agent-browser close\n\n    sleep 1  # Polite delay\ndone\n```\n\n### Corporate Network Access\n\n```bash\n#!/bin/bash\n# Access internal sites via corporate proxy\n\nexport HTTP_PROXY=\"http://corpproxy.company.com:8080\"\nexport HTTPS_PROXY=\"http://corpproxy.company.com:8080\"\nexport NO_PROXY=\"localhost,127.0.0.1,.company.com\"\n\n# External sites go through proxy\nagent-browser open https://external-vendor.com\n\n# Internal sites bypass proxy\nagent-browser open https://intranet.company.com\n```\n\n## Verifying Proxy Connection\n\n```bash\n# Check your apparent IP\nagent-browser open https://httpbin.org/ip\nagent-browser get text body\n# Should show proxy's IP, not your real IP\n```\n\n## Troubleshooting\n\n### Proxy Connection Failed\n\n```bash\n# Test proxy connectivity first\ncurl -x http://proxy.example.com:8080 https://httpbin.org/ip\n\n# Check if proxy requires auth\nexport HTTP_PROXY=\"http://user:pass@proxy.example.com:8080\"\n```\n\n### SSL/TLS Errors Through Proxy\n\nSome proxies perform SSL inspection. If you encounter certificate errors:\n\n```bash\n# For testing only - not recommended for production\nagent-browser open https://example.com --ignore-https-errors\n```\n\n### Slow Performance\n\n```bash\n# Use proxy only when necessary\nexport NO_PROXY=\"*.cdn.com,*.static.com\"  # Direct CDN access\n```\n\n## Best Practices\n\n1. **Use environment variables** - Don't hardcode proxy credentials\n2. **Set NO_PROXY appropriately** - Avoid routing local traffic through proxy\n3. **Test proxy before automation** - Verify connectivity with simple requests\n4. **Handle proxy failures gracefully** - Implement retry logic for unstable proxies\n5. **Rotate proxies for large scraping jobs** - Distribute load and avoid bans\n\n<!-- File: .agents/skills/agent-browser/references/session-management.md (Tokens: ~911 | Category: anthropic-skill) -->\n# Session Management\n\nRun multiple isolated browser sessions concurrently with state persistence.\n\n## Named Sessions\n\nUse `--session` flag to isolate browser contexts:\n\n```bash\n# Session 1: Authentication flow\nagent-browser --session auth open https://app.example.com/login\n\n# Session 2: Public browsing (separate cookies, storage)\nagent-browser --session public open https://example.com\n\n# Commands are isolated by session\nagent-browser --session auth fill @e1 \"user@example.com\"\nagent-browser --session public get text body\n```\n\n## Session Isolation Properties\n\nEach session has independent:\n- Cookies\n- LocalStorage / SessionStorage\n- IndexedDB\n- Cache\n- Browsing history\n- Open tabs\n\n## Session State Persistence\n\n### Save Session State\n\n```bash\n# Save cookies, storage, and auth state\nagent-browser state save /path/to/auth-state.json\n```\n\n### Load Session State\n\n```bash\n# Restore saved state\nagent-browser state load /path/to/auth-state.json\n\n# Continue with authenticated session\nagent-browser open https://app.example.com/dashboard\n```\n\n### State File Contents\n\n```json\n{\n  \"cookies\": [...],\n  \"localStorage\": {...},\n  \"sessionStorage\": {...},\n  \"origins\": [...]\n}\n```\n\n## Common Patterns\n\n### Authenticated Session Reuse\n\n```bash\n#!/bin/bash\n# Save login state once, reuse many times\n\nSTATE_FILE=\"/tmp/auth-state.json\"\n\n# Check if we have saved state\nif [[ -f \"$STATE_FILE\" ]]; then\n    agent-browser state load \"$STATE_FILE\"\n    agent-browser open https://app.example.com/dashboard\nelse\n    # Perform login\n    agent-browser open https://app.example.com/login\n    agent-browser snapshot -i\n    agent-browser fill @e1 \"$USERNAME\"\n    agent-browser fill @e2 \"$PASSWORD\"\n    agent-browser click @e3\n    agent-browser wait --load networkidle\n\n    # Save for future use\n    agent-browser state save \"$STATE_FILE\"\nfi\n```\n\n### Concurrent Scraping\n\n```bash\n#!/bin/bash\n# Scrape multiple sites concurrently\n\n# Start all sessions\nagent-browser --session site1 open https://site1.com &\nagent-browser --session site2 open https://site2.com &\nagent-browser --session site3 open https://site3.com &\nwait\n\n# Extract from each\nagent-browser --session site1 get text body > site1.txt\nagent-browser --session site2 get text body > site2.txt\nagent-browser --session site3 get text body > site3.txt\n\n# Cleanup\nagent-browser --session site1 close\nagent-browser --session site2 close\nagent-browser --session site3 close\n```\n\n### A/B Testing Sessions\n\n```bash\n# Test different user experiences\nagent-browser --session variant-a open \"https://app.com?variant=a\"\nagent-browser --session variant-b open \"https://app.com?variant=b\"\n\n# Compare\nagent-browser --session variant-a screenshot /tmp/variant-a.png\nagent-browser --session variant-b screenshot /tmp/variant-b.png\n```\n\n## Default Session\n\nWhen `--session` is omitted, commands use the default session:\n\n```bash\n# These use the same default session\nagent-browser open https://example.com\nagent-browser snapshot -i\nagent-browser close  # Closes default session\n```\n\n## Session Cleanup\n\n```bash\n# Close specific session\nagent-browser --session auth close\n\n# List active sessions\nagent-browser session list\n```\n\n## Best Practices\n\n### 1. Name Sessions Semantically\n\n```bash\n# GOOD: Clear purpose\nagent-browser --session github-auth open https://github.com\nagent-browser --session docs-scrape open https://docs.example.com\n\n# AVOID: Generic names\nagent-browser --session s1 open https://github.com\n```\n\n### 2. Always Clean Up\n\n```bash\n# Close sessions when done\nagent-browser --session auth close\nagent-browser --session scrape close\n```\n\n### 3. Handle State Files Securely\n\n```bash\n# Don't commit state files (contain auth tokens!)\necho \"*.auth-state.json\" >> .gitignore\n\n# Delete after use\nrm /tmp/auth-state.json\n```\n\n### 4. Timeout Long Sessions\n\n```bash\n# Set timeout for automated scripts\ntimeout 60 agent-browser --session long-task get text body\n```\n\n<!-- File: .agents/skills/agent-browser/references/snapshot-refs.md (Tokens: ~1051 | Category: anthropic-skill) -->\n# Snapshot + Refs Workflow\n\nThe core innovation of agent-browser: compact element references that reduce context usage dramatically for AI agents.\n\n## How It Works\n\n### The Problem\nTraditional browser automation sends full DOM to AI agents:\n```\nFull DOM/HTML sent → AI parses → Generates CSS selector → Executes action\n~3000-5000 tokens per interaction\n```\n\n### The Solution\nagent-browser uses compact snapshots with refs:\n```\nCompact snapshot → @refs assigned → Direct ref interaction\n~200-400 tokens per interaction\n```\n\n## The Snapshot Command\n\n```bash\n# Basic snapshot (shows page structure)\nagent-browser snapshot\n\n# Interactive snapshot (-i flag) - RECOMMENDED\nagent-browser snapshot -i\n```\n\n### Snapshot Output Format\n\n```\nPage: Example Site - Home\nURL: https://example.com\n\n@e1 [header]\n  @e2 [nav]\n    @e3 [a] \"Home\"\n    @e4 [a] \"Products\"\n    @e5 [a] \"About\"\n  @e6 [button] \"Sign In\"\n\n@e7 [main]\n  @e8 [h1] \"Welcome\"\n  @e9 [form]\n    @e10 [input type=\"email\"] placeholder=\"Email\"\n    @e11 [input type=\"password\"] placeholder=\"Password\"\n    @e12 [button type=\"submit\"] \"Log In\"\n\n@e13 [footer]\n  @e14 [a] \"Privacy Policy\"\n```\n\n## Using Refs\n\nOnce you have refs, interact directly:\n\n```bash\n# Click the \"Sign In\" button\nagent-browser click @e6\n\n# Fill email input\nagent-browser fill @e10 \"user@example.com\"\n\n# Fill password\nagent-browser fill @e11 \"password123\"\n\n# Submit the form\nagent-browser click @e12\n```\n\n## Ref Lifecycle\n\n**IMPORTANT**: Refs are invalidated when the page changes!\n\n```bash\n# Get initial snapshot\nagent-browser snapshot -i\n# @e1 [button] \"Next\"\n\n# Click triggers page change\nagent-browser click @e1\n\n# MUST re-snapshot to get new refs!\nagent-browser snapshot -i\n# @e1 [h1] \"Page 2\"  ← Different element now!\n```\n\n## Best Practices\n\n### 1. Always Snapshot Before Interacting\n\n```bash\n# CORRECT\nagent-browser open https://example.com\nagent-browser snapshot -i          # Get refs first\nagent-browser click @e1            # Use ref\n\n# WRONG\nagent-browser open https://example.com\nagent-browser click @e1            # Ref doesn't exist yet!\n```\n\n### 2. Re-Snapshot After Navigation\n\n```bash\nagent-browser click @e5            # Navigates to new page\nagent-browser snapshot -i          # Get new refs\nagent-browser click @e1            # Use new refs\n```\n\n### 3. Re-Snapshot After Dynamic Changes\n\n```bash\nagent-browser click @e1            # Opens dropdown\nagent-browser snapshot -i          # See dropdown items\nagent-browser click @e7            # Select item\n```\n\n### 4. Snapshot Specific Regions\n\nFor complex pages, snapshot specific areas:\n\n```bash\n# Snapshot just the form\nagent-browser snapshot @e9\n```\n\n## Ref Notation Details\n\n```\n@e1 [tag type=\"value\"] \"text content\" placeholder=\"hint\"\n│    │   │             │               │\n│    │   │             │               └─ Additional attributes\n│    │   │             └─ Visible text\n│    │   └─ Key attributes shown\n│    └─ HTML tag name\n└─ Unique ref ID\n```\n\n### Common Patterns\n\n```\n@e1 [button] \"Submit\"                    # Button with text\n@e2 [input type=\"email\"]                 # Email input\n@e3 [input type=\"password\"]              # Password input\n@e4 [a href=\"/page\"] \"Link Text\"         # Anchor link\n@e5 [select]                             # Dropdown\n@e6 [textarea] placeholder=\"Message\"     # Text area\n@e7 [div class=\"modal\"]                  # Container (when relevant)\n@e8 [img alt=\"Logo\"]                     # Image\n@e9 [checkbox] checked                   # Checked checkbox\n@e10 [radio] selected                    # Selected radio\n```\n\n## Troubleshooting\n\n### \"Ref not found\" Error\n\n```bash\n# Ref may have changed - re-snapshot\nagent-browser snapshot -i\n```\n\n### Element Not Visible in Snapshot\n\n```bash\n# Scroll to reveal element\nagent-browser scroll --bottom\nagent-browser snapshot -i\n\n# Or wait for dynamic content\nagent-browser wait 1000\nagent-browser snapshot -i\n```\n\n### Too Many Elements\n\n```bash\n# Snapshot specific container\nagent-browser snapshot @e5\n\n# Or use get text for content-only extraction\nagent-browser get text @e5\n```\n\n<!-- File: .agents/skills/agent-browser/references/video-recording.md (Tokens: ~1112 | Category: anthropic-skill) -->\n# Recording Demos\n\nProduce polished, shareable demo videos with a visible cursor and minimal dead time.\n\nThis guide builds on the basics in `video-recording.md` with techniques specific to recording demos that look good to viewers.\n\n## Key Principles\n\n- **Minimize dead time** — chain commands with `&&` so tool-call overhead stays out of the video.\n- **Visible cursor** — headless Playwright has no cursor; inject a fake one via DOM.\n- **One chain per page** — navigation destroys the DOM cursor, so re-inject after every page change.\n- **No `sleep` calls** — tool execution provides enough natural pacing.\n\n## Step-by-step\n\n### 1. Set up the page BEFORE recording\n\nNavigate and snapshot **before** `record start`. Any time after recording starts is in the video.\n\n```bash\nagent-browser open <url>\n```\n```bash\nagent-browser snapshot -i\n```\n\nReview the snapshot to identify element refs (`@e1`, `@e2`, …) and plan the interaction sequence.\n\n### 2. Find element coordinates for mouse moves\n\nUse `getBoundingClientRect()` to get the (x, y) center of each target element. Do this **before** recording starts so the lookup doesn't appear in the video.\n\n```bash\nagent-browser eval \"(() => {\n  const el = document.querySelector('<selector>');\n  const r = el.getBoundingClientRect();\n  return JSON.stringify({ x: Math.round(r.x + r.width/2), y: Math.round(r.y + r.height/2) });\n})()\"\n```\n\n### 3. Inject a visible cursor\n\nHeadless Playwright doesn't render a cursor. Inject a DOM element that tracks mouse events:\n\n```bash\nagent-browser eval \"(() => {\n  const c = document.createElement('div');\n  c.style.cssText = 'width:20px;height:20px;border-radius:50%;background:rgba(255,50,50,0.85);position:fixed;top:290px;left:390px;z-index:999999;pointer-events:none;transition:top 0.15s ease,left 0.15s ease;box-shadow:0 0 8px rgba(255,50,50,0.5);';\n  document.body.appendChild(c);\n  document.addEventListener('mousemove', e => {\n    c.style.top=(e.clientY-10)+'px';\n    c.style.left=(e.clientX-10)+'px';\n  });\n})(); 'ok'\"\n```\n\n**Re-inject after every navigation** — page transitions destroy the DOM element.\n\n### 4. Chain all commands with `&&`\n\nEvery separate tool call adds seconds of dead time. Chain everything into as few bash calls as possible:\n\n```bash\nagent-browser record start ./demo.webm && agent-browser eval \"<inject cursor>\" && agent-browser mouse move 400 300 && agent-browser click @e1 && agent-browser mouse move 600 200 && agent-browser click @e2 && agent-browser record stop\n```\n\n### 5. Use `mouse move` before every click\n\nMove the cursor to the target first so viewers can follow the action:\n\n```bash\nagent-browser mouse move 623 52 && agent-browser click @e5\n```\n\n### 6. No `sleep` calls\n\nEvery millisecond counts. Tool execution itself provides enough natural pacing. Drop all `sleep` and `wait` calls entirely.\n\n### 7. One chain per page\n\nSince the cursor needs re-injection after navigation, structure as one `&&` chain per page. The recording persists across chains — only stop it in the final chain.\n\n```bash\n# Chain 1: first page → click navigates away\nagent-browser record start ./demo.webm && agent-browser eval \"<inject cursor>\" && agent-browser mouse move 400 300 && agent-browser click @e5\n```\n\n```bash\n# Chain 2: new page → finish\nagent-browser eval \"<inject cursor>\" && agent-browser mouse move 200 150 && agent-browser click @e3 && agent-browser scroll down 200 && agent-browser record stop\n```\n\n### 8. Serve the result\n\nCopy the `.webm` into `public/`, create a minimal viewer page, and share the sandbox URL.\n\n```bash\nmkdir -p public && cp demo.webm public/demo.webm\n```\n\nViewer page (`public/demo.html`):\n\n```html\n<!DOCTYPE html>\n<html>\n<head><title>Demo</title></head>\n<body style=\"margin:0;display:flex;justify-content:center;align-items:center;min-height:100vh;background:#111\">\n  <video controls autoplay style=\"max-width:90vw;max-height:90vh;border-radius:8px;box-shadow:0 4px 24px rgba(0,0,0,0.5)\" src=\"/demo.webm\"></video>\n</body>\n</html>\n```\n\n## Common Mistakes\n\n- **Recording before the page is ready** — navigate and snapshot first.\n- **Forgetting to re-inject the cursor after navigation** — the DOM element is gone.\n- **Separate tool calls instead of `&&` chains** — adds seconds of blank screen.\n- **Using `sleep` or `wait`** — unnecessary; tool execution provides natural pacing.\n- **Clicking without `mouse move`** — clicks without visible cursor movement look jarring.\n\n<!-- File: .agents/skills/agent-browser/SKILL.md (Tokens: ~2847 | Category: anthropic-skill) -->\n---\nname: agent-browser\ndescription: Automates browser interactions for web testing, form filling, screenshots, and data extraction. Use when the user needs to navigate websites, interact with web pages, fill forms, take screenshots, test web applications, or extract information from web pages.\nallowed-tools: Bash(agent-browser:*)\n---\n\n# Browser Automation with agent-browser\n\n## Quick start\n\n```bash\nagent-browser open <url>        # Navigate to page\nagent-browser snapshot -i       # Get interactive elements with refs\nagent-browser click @e1         # Click element by ref\nagent-browser fill @e2 \"text\"   # Fill input by ref\nagent-browser close             # Close browser\n```\n\n## Core workflow\n\n1. Navigate: `agent-browser open <url>`\n2. Snapshot: `agent-browser snapshot -i` (returns elements with refs like `@e1`, `@e2`)\n3. Interact using refs from the snapshot\n4. Re-snapshot after navigation or significant DOM changes\n\n## Commands\n\n### Navigation\n\n```bash\nagent-browser open <url>      # Navigate to URL (aliases: goto, navigate)\n                              # Supports: https://, http://, file://, about:, data://\n                              # Auto-prepends https:// if no protocol given\nagent-browser back            # Go back\nagent-browser forward         # Go forward\nagent-browser reload          # Reload page\nagent-browser close           # Close browser (aliases: quit, exit)\nagent-browser connect 9222    # Connect to browser via CDP port\n```\n\n### Snapshot (page analysis)\n\n```bash\nagent-browser snapshot            # Full accessibility tree\nagent-browser snapshot -i         # Interactive elements only (recommended)\nagent-browser snapshot -c         # Compact output\nagent-browser snapshot -d 3       # Limit depth to 3\nagent-browser snapshot -s \"#main\" # Scope to CSS selector\n```\n\n### Interactions (use @refs from snapshot)\n\n```bash\nagent-browser click @e1           # Click\nagent-browser dblclick @e1        # Double-click\nagent-browser focus @e1           # Focus element\nagent-browser fill @e2 \"text\"     # Clear and type\nagent-browser type @e2 \"text\"     # Type without clearing\nagent-browser press Enter         # Press key (alias: key)\nagent-browser press Control+a     # Key combination\nagent-browser keydown Shift       # Hold key down\nagent-browser keyup Shift         # Release key\nagent-browser hover @e1           # Hover\nagent-browser check @e1           # Check checkbox\nagent-browser uncheck @e1         # Uncheck checkbox\nagent-browser select @e1 \"value\"  # Select dropdown option\nagent-browser select @e1 \"a\" \"b\"  # Select multiple options\nagent-browser scroll down 500     # Scroll page (default: down 300px)\nagent-browser scrollintoview @e1  # Scroll element into view (alias: scrollinto)\nagent-browser drag @e1 @e2        # Drag and drop\nagent-browser upload @e1 file.pdf # Upload files\n```\n\n### Get information\n\n```bash\nagent-browser get text @e1        # Get element text\nagent-browser get html @e1        # Get innerHTML\nagent-browser get value @e1       # Get input value\nagent-browser get attr @e1 href   # Get attribute\nagent-browser get title           # Get page title\nagent-browser get url             # Get current URL\nagent-browser get count \".item\"   # Count matching elements\nagent-browser get box @e1         # Get bounding box\nagent-browser get styles @e1      # Get computed styles (font, color, bg, etc.)\n```\n\n### Check state\n\n```bash\nagent-browser is visible @e1      # Check if visible\nagent-browser is enabled @e1      # Check if enabled\nagent-browser is checked @e1      # Check if checked\n```\n\n### Screenshots & PDF\n\n```bash\nagent-browser screenshot          # Save to a temporary directory\nagent-browser screenshot path.png # Save to a specific path\nagent-browser screenshot --full   # Full page\nagent-browser pdf output.pdf      # Save as PDF\n```\n\n### Video recording\n\n```bash\nagent-browser record start ./demo.webm    # Start recording (uses current URL + state)\nagent-browser click @e1                   # Perform actions\nagent-browser record stop                 # Stop and save video\nagent-browser record restart ./take2.webm # Stop current + start new recording\n```\n\nRecording creates a fresh context but preserves cookies/storage from your session. If no URL is provided, it\nautomatically returns to your current page. For smooth demos, explore first, then start recording.\n\n### Wait\n\n```bash\nagent-browser wait @e1                     # Wait for element\nagent-browser wait 2000                    # Wait milliseconds\nagent-browser wait --text \"Success\"        # Wait for text (or -t)\nagent-browser wait --url \"**/dashboard\"    # Wait for URL pattern (or -u)\nagent-browser wait --load networkidle      # Wait for network idle (or -l)\nagent-browser wait --fn \"window.ready\"     # Wait for JS condition (or -f)\n```\n\n### Mouse control\n\n```bash\nagent-browser mouse move 100 200      # Move mouse\nagent-browser mouse down left         # Press button\nagent-browser mouse up left           # Release button\nagent-browser mouse wheel 100         # Scroll wheel\n```\n\n### Semantic locators (alternative to refs)\n\n```bash\nagent-browser find role button click --name \"Submit\"\nagent-browser find text \"Sign In\" click\nagent-browser find text \"Sign In\" click --exact      # Exact match only\nagent-browser find label \"Email\" fill \"user@test.com\"\nagent-browser find placeholder \"Search\" type \"query\"\nagent-browser find alt \"Logo\" click\nagent-browser find title \"Close\" click\nagent-browser find testid \"submit-btn\" click\nagent-browser find first \".item\" click\nagent-browser find last \".item\" click\nagent-browser find nth 2 \"a\" hover\n```\n\n### Browser settings\n\n```bash\nagent-browser set viewport 1920 1080          # Set viewport size\nagent-browser set device \"iPhone 14\"          # Emulate device\nagent-browser set geo 37.7749 -122.4194       # Set geolocation (alias: geolocation)\nagent-browser set offline on                  # Toggle offline mode\nagent-browser set headers '{\"X-Key\":\"v\"}'     # Extra HTTP headers\nagent-browser set credentials user pass       # HTTP basic auth (alias: auth)\nagent-browser set media dark                  # Emulate color scheme\nagent-browser set media light reduced-motion  # Light mode + reduced motion\n```\n\n### Cookies & Storage\n\n```bash\nagent-browser cookies                     # Get all cookies\nagent-browser cookies set name value      # Set cookie\nagent-browser cookies clear               # Clear cookies\nagent-browser storage local               # Get all localStorage\nagent-browser storage local key           # Get specific key\nagent-browser storage local set k v       # Set value\nagent-browser storage local clear         # Clear all\n```\n\n### Network\n\n```bash\nagent-browser network route <url>              # Intercept requests\nagent-browser network route <url> --abort      # Block requests\nagent-browser network route <url> --body '{}'  # Mock response\nagent-browser network unroute [url]            # Remove routes\nagent-browser network requests                 # View tracked requests\nagent-browser network requests --filter api    # Filter requests\n```\n\n### Tabs & Windows\n\n```bash\nagent-browser tab                 # List tabs\nagent-browser tab new [url]       # New tab\nagent-browser tab 2               # Switch to tab by index\nagent-browser tab close           # Close current tab\nagent-browser tab close 2         # Close tab by index\nagent-browser window new          # New window\n```\n\n### Frames\n\n```bash\nagent-browser frame \"#iframe\"     # Switch to iframe\nagent-browser frame main          # Back to main frame\n```\n\n### Dialogs\n\n```bash\nagent-browser dialog accept [text]  # Accept dialog\nagent-browser dialog dismiss        # Dismiss dialog\n```\n\n### JavaScript\n\n```bash\nagent-browser eval \"document.title\"   # Run JavaScript\n```\n\n## Global options\n\n```bash\nagent-browser --session <name> ...    # Isolated browser session\nagent-browser --json ...              # JSON output for parsing\nagent-browser --headed ...            # Show browser window (not headless)\nagent-browser --full ...              # Full page screenshot (-f)\nagent-browser --cdp <port> ...        # Connect via Chrome DevTools Protocol\nagent-browser -p <provider> ...       # Cloud browser provider (--provider)\nagent-browser --proxy <url> ...       # Use proxy server\nagent-browser --headers <json> ...    # HTTP headers scoped to URL's origin\nagent-browser --executable-path <p>   # Custom browser executable\nagent-browser --extension <path> ...  # Load browser extension (repeatable)\nagent-browser --help                  # Show help (-h)\nagent-browser --version               # Show version (-V)\nagent-browser <command> --help        # Show detailed help for a command\n```\n\n### Proxy support\n\n```bash\nagent-browser --proxy http://proxy.com:8080 open example.com\nagent-browser --proxy http://user:pass@proxy.com:8080 open example.com\nagent-browser --proxy socks5://proxy.com:1080 open example.com\n```\n\n## Environment variables\n\n```bash\nAGENT_BROWSER_SESSION=\"mysession\"            # Default session name\nAGENT_BROWSER_EXECUTABLE_PATH=\"/path/chrome\" # Custom browser path\nAGENT_BROWSER_EXTENSIONS=\"/ext1,/ext2\"       # Comma-separated extension paths\nAGENT_BROWSER_PROVIDER=\"your-cloud-browser-provider\"  # Cloud browser provider (select browseruse or browserbase)\nAGENT_BROWSER_STREAM_PORT=\"9223\"             # WebSocket streaming port\nAGENT_BROWSER_HOME=\"/path/to/agent-browser\"  # Custom install location (for daemon.js)\n```\n\n## Example: Form submission\n\n```bash\nagent-browser open https://example.com/form\nagent-browser snapshot -i\n# Output shows: textbox \"Email\" [ref=e1], textbox \"Password\" [ref=e2], button \"Submit\" [ref=e3]\n\nagent-browser fill @e1 \"user@example.com\"\nagent-browser fill @e2 \"password123\"\nagent-browser click @e3\nagent-browser wait --load networkidle\nagent-browser snapshot -i  # Check result\n```\n\n## Example: Authentication with saved state\n\n```bash\n# Login once\nagent-browser open https://app.example.com/login\nagent-browser snapshot -i\nagent-browser fill @e1 \"username\"\nagent-browser fill @e2 \"password\"\nagent-browser click @e3\nagent-browser wait --url \"**/dashboard\"\nagent-browser state save auth.json\n\n# Later sessions: load saved state\nagent-browser state load auth.json\nagent-browser open https://app.example.com/dashboard\n```\n\n## Sessions (parallel browsers)\n\n```bash\nagent-browser --session test1 open site-a.com\nagent-browser --session test2 open site-b.com\nagent-browser session list\n```\n\n## JSON output (for parsing)\n\nAdd `--json` for machine-readable output:\n\n```bash\nagent-browser snapshot -i --json\nagent-browser get text @e1 --json\n```\n\n## Debugging\n\n```bash\nagent-browser --headed open example.com   # Show browser window\nagent-browser --cdp 9222 snapshot         # Connect via CDP port\nagent-browser connect 9222                # Alternative: connect command\nagent-browser console                     # View console messages\nagent-browser console --clear             # Clear console\nagent-browser errors                      # View page errors\nagent-browser errors --clear              # Clear errors\nagent-browser highlight @e1               # Highlight element\nagent-browser trace start                 # Start recording trace\nagent-browser trace stop trace.zip        # Stop and save trace\nagent-browser record start ./debug.webm   # Record video from current page\nagent-browser record stop                 # Save recording\n```\n\n## Deep-dive documentation\n\nFor detailed patterns and best practices, see:\n\n| Reference | Description |\n|-----------|-------------|\n| [references/snapshot-refs.md](references/snapshot-refs.md) | Ref lifecycle, invalidation rules, troubleshooting |\n| [references/session-management.md](references/session-management.md) | Parallel sessions, state persistence, concurrent scraping |\n| [references/authentication.md](references/authentication.md) | Login flows, OAuth, 2FA handling, state reuse |\n| [references/video-recording.md](references/video-recording.md) | Recording workflows for debugging and documentation |\n| [references/proxy-support.md](references/proxy-support.md) | Proxy configuration, geo-testing, rotating proxies |\n\n## Ready-to-use templates\n\nExecutable workflow scripts for common patterns:\n\n| Template | Description |\n|----------|-------------|\n| [templates/form-automation.sh](templates/form-automation.sh) | Form filling with validation |\n| [templates/authenticated-session.sh](templates/authenticated-session.sh) | Login once, reuse state |\n| [templates/capture-workflow.sh](templates/capture-workflow.sh) | Content extraction with screenshots |\n\nUsage:\n```bash\n./templates/form-automation.sh https://example.com/form\n./templates/authenticated-session.sh https://app.example.com/login\n./templates/capture-workflow.sh https://example.com ./output\n```\n\n## HTTPS Certificate Errors\n\nFor sites with self-signed or invalid certificates:\n```bash\nagent-browser open https://localhost:8443 --ignore-https-errors\n```\n\n<!-- File: .agents/skills/ai-sdk/references/ai-gateway.md (Tokens: ~623 | Category: anthropic-skill) -->\n---\ntitle: Vercel AI Gateway\ndescription: Reference for using Vercel AI Gateway with the AI SDK.\n---\n\n# Vercel AI Gateway\n\nThe Vercel AI Gateway is the fastest way to get started with the AI SDK. It provides access to models from OpenAI, Anthropic, Google, and other providers through a single API.\n\n## Authentication\n\nAuthenticate with OIDC (for Vercel deployments) or an [AI Gateway API key](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai-gateway%2Fapi-keys&title=AI+Gateway+API+Keys):\n\n```env filename=\".env.local\"\nAI_GATEWAY_API_KEY=your_api_key_here\n```\n\n## Usage\n\nThe AI Gateway is the default global provider, so you can access models using a simple string:\n\n```ts\nimport { generateText } from 'ai';\n\nconst { text } = await generateText({\n  model: 'anthropic/claude-sonnet-4.5',\n  prompt: 'What is love?',\n});\n```\n\nYou can also explicitly import and use the gateway provider:\n\n```ts\n// Option 1: Import from 'ai' package (included by default)\nimport { gateway } from 'ai';\nmodel: gateway('anthropic/claude-sonnet-4.5');\n\n// Option 2: Install and import from '@ai-sdk/gateway' package\nimport { gateway } from '@ai-sdk/gateway';\nmodel: gateway('anthropic/claude-sonnet-4.5');\n```\n\n## Find Available Models\n\n**Important**: Always fetch the current model list before writing code. Never use model IDs from memory - they may be outdated.\n\nList all available models through the gateway API:\n\n```bash\ncurl https://ai-gateway.vercel.sh/v1/models\n```\n\nFilter by provider using `jq`. **Do not truncate with `head`** - always fetch the full list to find the latest models:\n\n```bash\n# Anthropic models\ncurl -s https://ai-gateway.vercel.sh/v1/models | jq -r '[.data[] | select(.id | startswith(\"anthropic/\")) | .id] | reverse | .[]'\n\n# OpenAI models\ncurl -s https://ai-gateway.vercel.sh/v1/models | jq -r '[.data[] | select(.id | startswith(\"openai/\")) | .id] | reverse | .[]'\n\n# Google models\ncurl -s https://ai-gateway.vercel.sh/v1/models | jq -r '[.data[] | select(.id | startswith(\"google/\")) | .id] | reverse | .[]'\n```\n\nWhen multiple versions of a model exist, use the one with the highest version number (e.g., prefer `claude-sonnet-4-5` over `claude-sonnet-4` over `claude-3-5-sonnet`).\n\n<!-- File: .agents/skills/ai-sdk/references/common-errors.md (Tokens: ~2784 | Category: anthropic-skill) -->\n---\ntitle: Common Errors\ndescription: Reference for common AI SDK errors and how to resolve them.\n---\n\n# Common Errors\n\n## `maxTokens` → `maxOutputTokens`\n\n```typescript\n// ❌ Incorrect\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  maxTokens: 512, // deprecated: use `maxOutputTokens` instead\n  prompt: 'Write a short story',\n});\n\n// ✅ Correct\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  maxOutputTokens: 512,\n  prompt: 'Write a short story',\n});\n```\n\n## `maxSteps` → `stopWhen: stepCountIs(n)`\n\n```typescript\n// ❌ Incorrect\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  tools: { weather },\n  maxSteps: 5, // deprecated: use `stopWhen: stepCountIs(n)` instead\n  prompt: 'What is the weather in NYC?',\n});\n\n// ✅ Correct\nimport { generateText, stepCountIs } from 'ai';\n\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  tools: { weather },\n  stopWhen: stepCountIs(5),\n  prompt: 'What is the weather in NYC?',\n});\n```\n\n## `parameters` → `inputSchema` (in tool definition)\n\n```typescript\n// ❌ Incorrect\nconst weatherTool = tool({\n  description: 'Get weather for a location',\n  parameters: z.object({\n    // deprecated: use `inputSchema` instead\n    location: z.string(),\n  }),\n  execute: async ({ location }) => ({ location, temp: 72 }),\n});\n\n// ✅ Correct\nconst weatherTool = tool({\n  description: 'Get weather for a location',\n  inputSchema: z.object({\n    location: z.string(),\n  }),\n  execute: async ({ location }) => ({ location, temp: 72 }),\n});\n```\n\n## `generateObject` → `generateText` with `output`\n\n`generateObject` is deprecated. Use `generateText` with the `output` option instead.\n\n```typescript\n// ❌ Deprecated\nimport { generateObject } from 'ai'; // deprecated: use `generateText` with `output` instead\n\nconst result = await generateObject({\n  // deprecated function\n  model: 'anthropic/claude-opus-4.5',\n  schema: z.object({\n    // deprecated: use `Output.object({ schema })` instead\n    recipe: z.object({\n      name: z.string(),\n      ingredients: z.array(z.string()),\n    }),\n  }),\n  prompt: 'Generate a recipe for chocolate cake',\n});\n\n// ✅ Correct\nimport { generateText, Output } from 'ai';\n\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  output: Output.object({\n    schema: z.object({\n      recipe: z.object({\n        name: z.string(),\n        ingredients: z.array(z.string()),\n      }),\n    }),\n  }),\n  prompt: 'Generate a recipe for chocolate cake',\n});\n\nconsole.log(result.output); // typed object\n```\n\n## Manual JSON parsing → `generateText` with `output`\n\n```typescript\n// ❌ Incorrect\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  prompt: `Extract the user info as JSON: { \"name\": string, \"age\": number }\n\n  Input: John is 25 years old`,\n});\nconst parsed = JSON.parse(result.text);\n\n// ✅ Correct\nimport { generateText, Output } from 'ai';\n\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  output: Output.object({\n    schema: z.object({\n      name: z.string(),\n      age: z.number(),\n    }),\n  }),\n  prompt: 'Extract the user info: John is 25 years old',\n});\n\nconsole.log(result.output); // { name: 'John', age: 25 }\n```\n\n## Other `output` options\n\n```typescript\n// Output.array - for generating arrays of items\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  output: Output.array({\n    element: z.object({\n      city: z.string(),\n      country: z.string(),\n    }),\n  }),\n  prompt: 'List 5 capital cities',\n});\n\n// Output.choice - for selecting from predefined options\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  output: Output.choice({\n    options: ['positive', 'negative', 'neutral'] as const,\n  }),\n  prompt: 'Classify the sentiment: I love this product!',\n});\n\n// Output.json - for untyped JSON output\nconst result = await generateText({\n  model: 'anthropic/claude-opus-4.5',\n  output: Output.json(),\n  prompt: 'Return some JSON data',\n});\n```\n\n## `toDataStreamResponse` → `toUIMessageStreamResponse`\n\nWhen using `useChat` on the frontend, use `toUIMessageStreamResponse()` instead of `toDataStreamResponse()`. The UI message stream format is designed to work with the chat UI components and handles message state correctly.\n\n```typescript\n// ❌ Incorrect (when using useChat)\nconst result = streamText({\n  // config\n});\n\nreturn result.toDataStreamResponse(); // deprecated for useChat: use toUIMessageStreamResponse\n\n// ✅ Correct\nconst result = streamText({\n  // config\n});\n\nreturn result.toUIMessageStreamResponse();\n```\n\n## Removed managed input state in `useChat`\n\nThe `useChat` hook no longer manages input state internally. You must now manage input state manually.\n\n```tsx\n// ❌ Deprecated\nimport { useChat } from '@ai-sdk/react';\n\nexport default function Page() {\n  const {\n    input, // deprecated: manage input state manually with useState\n    handleInputChange, // deprecated: use custom onChange handler\n    handleSubmit, // deprecated: use sendMessage() instead\n  } = useChat({\n    api: '/api/chat', // deprecated: use `transport: new DefaultChatTransport({ api })` instead\n  });\n\n  return (\n    <form onSubmit={handleSubmit}>\n      <input value={input} onChange={handleInputChange} />\n      <button type=\"submit\">Send</button>\n    </form>\n  );\n}\n\n// ✅ Correct\nimport { useChat } from '@ai-sdk/react';\nimport { DefaultChatTransport } from 'ai';\nimport { useState } from 'react';\n\nexport default function Page() {\n  const [input, setInput] = useState('');\n  const { sendMessage } = useChat({\n    transport: new DefaultChatTransport({ api: '/api/chat' }),\n  });\n\n  const handleSubmit = e => {\n    e.preventDefault();\n    sendMessage({ text: input });\n    setInput('');\n  };\n\n  return (\n    <form onSubmit={handleSubmit}>\n      <input value={input} onChange={e => setInput(e.target.value)} />\n      <button type=\"submit\">Send</button>\n    </form>\n  );\n}\n```\n\n## `tool-invocation` → `tool-{toolName}` (typed tool parts)\n\nWhen rendering messages with `useChat`, use the typed tool part names (`tool-{toolName}`) instead of the generic `tool-invocation` type. This provides better type safety and access to tool-specific input/output types.\n\n> For end-to-end type-safety, see [Type-Safe Agents](type-safe-agents.md).\n\nTyped tool parts also use different property names:\n\n- `part.args` → `part.input`\n- `part.result` → `part.output`\n\n```tsx\n// ❌ Incorrect - using generic tool-invocation\n{\n  message.parts.map((part, i) => {\n    switch (part.type) {\n      case 'text':\n        return <div key={`${message.id}-${i}`}>{part.text}</div>;\n      case 'tool-invocation': // deprecated: use typed tool parts instead\n        return (\n          <pre key={`${message.id}-${i}`}>\n            {JSON.stringify(part.toolInvocation, null, 2)}\n          </pre>\n        );\n    }\n  });\n}\n\n// ✅ Correct - using typed tool parts (recommended)\n{\n  message.parts.map(part => {\n    switch (part.type) {\n      case 'text':\n        return part.text;\n      case 'tool-askForConfirmation':\n        // handle askForConfirmation tool\n        break;\n      case 'tool-getWeatherInformation':\n        // handle getWeatherInformation tool\n        break;\n    }\n  });\n}\n\n// ✅ Alternative - using isToolUIPart as a catch-all\nimport { isToolUIPart } from 'ai';\n\n{\n  message.parts.map(part => {\n    if (part.type === 'text') {\n      return part.text;\n    }\n    if (isToolUIPart(part)) {\n      // handle any tool part generically\n      return (\n        <div key={part.toolCallId}>\n          {part.toolName}: {part.state}\n        </div>\n      );\n    }\n  });\n}\n```\n\n## `useChat` state-dependent property access\n\nTool part properties are only available in certain states. TypeScript will error if you access them without checking state first.\n\n```tsx\n// ❌ Incorrect - input may be undefined during streaming\n// TS18048: 'part.input' is possibly 'undefined'\nif (part.type === 'tool-getWeather') {\n  const location = part.input.location;\n}\n\n// ✅ Correct - check for input-available or output-available\nif (\n  part.type === 'tool-getWeather' &&\n  (part.state === 'input-available' || part.state === 'output-available')\n) {\n  const location = part.input.location;\n}\n\n// ❌ Incorrect - output is only available after execution\n// TS18048: 'part.output' is possibly 'undefined'\nif (part.type === 'tool-getWeather') {\n  const weather = part.output;\n}\n\n// ✅ Correct - check for output-available\nif (part.type === 'tool-getWeather' && part.state === 'output-available') {\n  const location = part.input.location;\n  const weather = part.output;\n}\n```\n\n## `part.toolInvocation.args` → `part.input`\n\n```tsx\n// ❌ Incorrect\nif (part.type === 'tool-invocation') {\n  // deprecated: use `part.input` on typed tool parts instead\n  const location = part.toolInvocation.args.location;\n}\n\n// ✅ Correct\nif (\n  part.type === 'tool-getWeather' &&\n  (part.state === 'input-available' || part.state === 'output-available')\n) {\n  const location = part.input.location;\n}\n```\n\n## `part.toolInvocation.result` → `part.output`\n\n```tsx\n// ❌ Incorrect\nif (part.type === 'tool-invocation') {\n  // deprecated: use `part.output` on typed tool parts instead\n  const weather = part.toolInvocation.result;\n}\n\n// ✅ Correct\nif (part.type === 'tool-getWeather' && part.state === 'output-available') {\n  const weather = part.output;\n}\n```\n\n## `part.toolInvocation.toolCallId` → `part.toolCallId`\n\n```tsx\n// ❌ Incorrect\nif (part.type === 'tool-invocation') {\n  // deprecated: use `part.toolCallId` on typed tool parts instead\n  const id = part.toolInvocation.toolCallId;\n}\n\n// ✅ Correct\nif (part.type === 'tool-getWeather') {\n  const id = part.toolCallId;\n}\n```\n\n## Tool invocation states renamed\n\n```tsx\n// ❌ Incorrect\nswitch (part.toolInvocation.state) {\n  case 'partial-call': // deprecated: use `input-streaming` instead\n    return <div>Loading...</div>;\n  case 'call': // deprecated: use `input-available` instead\n    return <div>Executing...</div>;\n  case 'result': // deprecated: use `output-available` instead\n    return <div>Done</div>;\n}\n\n// ✅ Correct\nswitch (part.state) {\n  case 'input-streaming':\n    return <div>Loading...</div>;\n  case 'input-available':\n    return <div>Executing...</div>;\n  case 'output-available':\n    return <div>Done</div>;\n}\n```\n\n## `addToolResult` → `addToolOutput`\n\n```tsx\n// ❌ Incorrect\naddToolResult({\n  // deprecated: use `addToolOutput` instead\n  toolCallId: part.toolInvocation.toolCallId,\n  result: 'Yes, confirmed.', // deprecated: use `output` instead\n});\n\n// ✅ Correct\naddToolOutput({\n  tool: 'askForConfirmation',\n  toolCallId: part.toolCallId,\n  output: 'Yes, confirmed.',\n});\n```\n\n## `messages` → `uiMessages` in `createAgentUIStreamResponse`\n\n```typescript\n// ❌ Incorrect\nreturn createAgentUIStreamResponse({\n  agent: myAgent,\n  messages, // incorrect: use `uiMessages` instead\n});\n\n// ✅ Correct\nreturn createAgentUIStreamResponse({\n  agent: myAgent,\n  uiMessages: messages,\n});\n```\n\n<!-- File: .agents/skills/ai-sdk/references/devtools.md (Tokens: ~296 | Category: anthropic-skill) -->\n---\ntitle: AI SDK DevTools\ndescription: Debug AI SDK calls by inspecting captured runs and steps.\n---\n\n# AI SDK DevTools\n\n## Why Use DevTools\n\nDevTools captures all AI SDK calls (`generateText`, `streamText`, `ToolLoopAgent`) to a local JSON file. This lets you inspect LLM requests, responses, tool calls, and multi-step interactions without manually logging.\n\n## Setup\n\nRequires AI SDK 6. Install `@ai-sdk/devtools` using your project's package manager.\n\nWrap your model with the middleware:\n\n```ts\nimport { wrapLanguageModel, gateway } from 'ai';\nimport { devToolsMiddleware } from '@ai-sdk/devtools';\n\nconst model = wrapLanguageModel({\n  model: gateway('anthropic/claude-sonnet-4.5'),\n  middleware: devToolsMiddleware(),\n});\n```\n\n## Viewing Captured Data\n\nAll runs and steps are saved to:\n\n```\n.devtools/generations.json\n```\n\nRead this file directly to inspect captured data:\n\n```bash\ncat .devtools/generations.json | jq\n```\n\nOr launch the web UI:\n\n```bash\nnpx @ai-sdk/devtools\n# Open http://localhost:4983\n```\n\n## Data Structure\n\n- **Run**: A complete multi-step interaction grouped by initial prompt\n- **Step**: A single LLM call within a run (includes input, output, tool calls, token usage)\n\n<!-- File: .agents/skills/ai-sdk/references/type-safe-agents.md (Tokens: ~1242 | Category: anthropic-skill) -->\n---\ntitle: Type-Safe useChat with Agents\ndescription: Build end-to-end type-safe agents by inferring UIMessage types from your agent definition.\n---\n\n# Type-Safe useChat with Agents\n\nBuild end-to-end type-safe agents by inferring `UIMessage` types from your agent definition for type-safe UI rendering with `useChat`.\n\n## Recommended Structure\n\n```\nlib/\n  agents/\n    my-agent.ts       # Agent definition + type export\n  tools/\n    weather-tool.ts   # Individual tool definitions\n    calculator-tool.ts\n```\n\n## Define Tools\n\n```ts\n// lib/tools/weather-tool.ts\nimport { tool } from 'ai';\nimport { z } from 'zod';\n\nexport const weatherTool = tool({\n  description: 'Get current weather for a location',\n  inputSchema: z.object({\n    location: z.string().describe('City name'),\n  }),\n  execute: async ({ location }) => {\n    return { temperature: 72, condition: 'sunny', location };\n  },\n});\n```\n\n## Define Agent and Export Type\n\n```ts\n// lib/agents/my-agent.ts\nimport { ToolLoopAgent, InferAgentUIMessage } from 'ai';\nimport { weatherTool } from '../tools/weather-tool';\nimport { calculatorTool } from '../tools/calculator-tool';\n\nexport const myAgent = new ToolLoopAgent({\n  model: 'anthropic/claude-sonnet-4',\n  instructions: 'You are a helpful assistant.',\n  tools: {\n    weather: weatherTool,\n    calculator: calculatorTool,\n  },\n});\n\n// Infer the UIMessage type from the agent\nexport type MyAgentUIMessage = InferAgentUIMessage<typeof myAgent>;\n```\n\n### With Custom Metadata\n\n```ts\n// lib/agents/my-agent.ts\nimport { z } from 'zod';\n\nconst metadataSchema = z.object({\n  createdAt: z.number(),\n  model: z.string().optional(),\n});\n\ntype MyMetadata = z.infer<typeof metadataSchema>;\n\nexport type MyAgentUIMessage = InferAgentUIMessage<typeof myAgent, MyMetadata>;\n```\n\n## Use with `useChat`\n\n```tsx\n// app/chat.tsx\nimport { useChat } from '@ai-sdk/react';\nimport type { MyAgentUIMessage } from '@/lib/agents/my-agent';\n\nexport function Chat() {\n  const { messages } = useChat<MyAgentUIMessage>();\n\n  return (\n    <div>\n      {messages.map(message => (\n        <Message key={message.id} message={message} />\n      ))}\n    </div>\n  );\n}\n```\n\n## Rendering Parts with Type Safety\n\nTool parts are typed as `tool-{toolName}` based on your agent's tools:\n\n```tsx\nfunction Message({ message }: { message: MyAgentUIMessage }) {\n  return (\n    <div>\n      {message.parts.map((part, i) => {\n        switch (part.type) {\n          case 'text':\n            return <p key={i}>{part.text}</p>;\n\n          case 'tool-weather':\n            // part.input and part.output are fully typed\n            if (part.state === 'output-available') {\n              return (\n                <div key={i}>\n                  Weather in {part.input.location}: {part.output.temperature}F\n                </div>\n              );\n            }\n            return <div key={i}>Loading weather...</div>;\n\n          case 'tool-calculator':\n            // TypeScript knows this is the calculator tool\n            return <div key={i}>Calculating...</div>;\n\n          default:\n            return null;\n        }\n      })}\n    </div>\n  );\n}\n```\n\nThe `part.type` discriminant narrows the type, giving you autocomplete and type checking for `input` and `output` based on each tool's schema.\n\n## Splitting Tool Rendering into Components\n\nWhen rendering many tools, you may want to split each tool into its own component. Use `UIToolInvocation<TOOL>` to derive a typed invocation from your tool and export it alongside the tool definition:\n\n```ts\n// lib/tools/weather-tool.ts\nimport { tool, UIToolInvocation } from 'ai';\nimport { z } from 'zod';\n\nexport const weatherTool = tool({\n  description: 'Get current weather for a location',\n  inputSchema: z.object({\n    location: z.string().describe('City name'),\n  }),\n  execute: async ({ location }) => {\n    return { temperature: 72, condition: 'sunny', location };\n  },\n});\n\n// Export the invocation type for use in UI components\nexport type WeatherToolInvocation = UIToolInvocation<typeof weatherTool>;\n```\n\nThen import only the type in your component:\n\n```tsx\n// components/weather-tool.tsx\nimport type { WeatherToolInvocation } from '@/lib/tools/weather-tool';\n\nexport function WeatherToolComponent({\n  invocation,\n}: {\n  invocation: WeatherToolInvocation;\n}) {\n  // invocation.input and invocation.output are fully typed\n  if (invocation.state === 'output-available') {\n    return (\n      <div>\n        Weather in {invocation.input.location}: {invocation.output.temperature}F\n      </div>\n    );\n  }\n  return <div>Loading weather for {invocation.input?.location}...</div>;\n}\n```\n\nUse the component in your message renderer:\n\n```tsx\nfunction Message({ message }: { message: MyAgentUIMessage }) {\n  return (\n    <div>\n      {message.parts.map((part, i) => {\n        switch (part.type) {\n          case 'text':\n            return <p key={i}>{part.text}</p>;\n          case 'tool-weather':\n            return <WeatherToolComponent key={i} invocation={part} />;\n          case 'tool-calculator':\n            return <CalculatorToolComponent key={i} invocation={part} />;\n          default:\n            return null;\n        }\n      })}\n    </div>\n  );\n}\n```\n\nThis approach keeps your tool rendering logic organized while maintaining full type safety, without needing to import the tool implementation into your UI components.\n\n<!-- File: .agents/skills/ai-sdk/SKILL.md (Tokens: ~1133 | Category: anthropic-skill) -->\n---\nname: ai-sdk\ndescription: 'Answer questions about the AI SDK and help build AI-powered features. Use when developers: (1) Ask about AI SDK functions like generateText, streamText, ToolLoopAgent, embed, or tools, (2) Want to build AI agents, chatbots, RAG systems, or text generation features, (3) Have questions about AI providers (OpenAI, Anthropic, Google, etc.), streaming, tool calling, structured output, or embeddings, (4) Use React hooks like useChat or useCompletion. Triggers on: \"AI SDK\", \"Vercel AI SDK\", \"generateText\", \"streamText\", \"add AI to my app\", \"build an agent\", \"tool calling\", \"structured output\", \"useChat\".'\n---\n\n## Prerequisites\n\nBefore searching docs, check if `node_modules/ai/docs/` exists. If not, install **only** the `ai` package using the project's package manager (e.g., `pnpm add ai`).\n\nDo not install other packages at this stage. Provider packages (e.g., `@ai-sdk/openai`) and client packages (e.g., `@ai-sdk/react`) should be installed later when needed based on user requirements.\n\n## Critical: Do Not Trust Internal Knowledge\n\nEverything you know about the AI SDK is outdated or wrong. Your training data contains obsolete APIs, deprecated patterns, and incorrect usage.\n\n**When working with the AI SDK:**\n\n1. Ensure `ai` package is installed (see Prerequisites)\n2. Search `node_modules/ai/docs/` and `node_modules/ai/src/` for current APIs\n3. If not found locally, search ai-sdk.dev documentation (instructions below)\n4. Never rely on memory - always verify against source code or docs\n5. **`useChat` has changed significantly** - check [Common Errors](references/common-errors.md) before writing client code\n6. When deciding which model and provider to use (e.g. OpenAI, Anthropic, Gemini), use the Vercel AI Gateway provider unless the user specifies otherwise. See [AI Gateway Reference](references/ai-gateway.md) for usage details.\n7. **Always fetch current model IDs** - Never use model IDs from memory. Before writing code that uses a model, run `curl -s https://ai-gateway.vercel.sh/v1/models | jq -r '[.data[] | select(.id | startswith(\"provider/\")) | .id] | reverse | .[]'` (replacing `provider` with the relevant provider like `anthropic`, `openai`, or `google`) to get the full list with newest models first. Use the model with the highest version number (e.g., `claude-sonnet-4-5` over `claude-sonnet-4` over `claude-3-5-sonnet`).\n8. Run typecheck after changes to ensure code is correct\n9. **Be minimal** - Only specify options that differ from defaults. When unsure of defaults, check docs or source rather than guessing or over-specifying.\n\nIf you cannot find documentation to support your answer, state that explicitly.\n\n## Finding Documentation\n\n### ai@6.0.34+\n\nSearch bundled docs and source in `node_modules/ai/`:\n\n- **Docs**: `grep \"query\" node_modules/ai/docs/`\n- **Source**: `grep \"query\" node_modules/ai/src/`\n\nProvider packages include docs at `node_modules/@ai-sdk/<provider>/docs/`.\n\n### Earlier versions\n\n1. Search: `https://ai-sdk.dev/api/search-docs?q=your_query`\n2. Fetch `.md` URLs from results (e.g., `https://ai-sdk.dev/docs/agents/building-agents.md`)\n\n## When Typecheck Fails\n\n**Before searching source code**, grep [Common Errors](references/common-errors.md) for the failing property or function name. Many type errors are caused by deprecated APIs documented there.\n\nIf not found in common-errors.md:\n\n1. Search `node_modules/ai/src/` and `node_modules/ai/docs/`\n2. Search ai-sdk.dev (for earlier versions or if not found locally)\n\n## Building and Consuming Agents\n\n### Creating Agents\n\nAlways use the `ToolLoopAgent` pattern. Search `node_modules/ai/docs/` for current agent creation APIs.\n\n**File conventions**: See [type-safe-agents.md](references/type-safe-agents.md) for where to save agents and tools.\n\n**Type Safety**: When consuming agents with `useChat`, always use `InferAgentUIMessage<typeof agent>` for type-safe tool results. See [reference](references/type-safe-agents.md).\n\n### Consuming Agents (Framework-Specific)\n\nBefore implementing agent consumption:\n\n1. Check `package.json` to detect the project's framework/stack\n2. Search documentation for the framework's quickstart guide\n3. Follow the framework-specific patterns for streaming, API routes, and client integration\n\n## References\n\n- [Common Errors](references/common-errors.md) - Renamed parameters reference (parameters → inputSchema, etc.)\n- [AI Gateway](references/ai-gateway.md) - Gateway setup and usage\n- [Type-Safe Agents with useChat](references/type-safe-agents.md) - End-to-end type safety with InferAgentUIMessage\n- [DevTools](references/devtools.md) - Set up local debugging and observability (development only)\n\n<!-- File: .agents/skills/baseline-ui/SKILL.md (Tokens: ~773 | Category: anthropic-skill) -->\n---\nname: baseline-ui\ndescription: Validates animation durations, enforces typography scale, checks component accessibility, and prevents layout anti-patterns in Tailwind CSS projects. Use when building UI components, reviewing CSS utilities, styling React views, or enforcing design consistency.\n---\n\n# Baseline UI\n\nEnforces an opinionated UI baseline to prevent AI-generated interface slop.\n\n## How to use\n\n- `/baseline-ui`\n  Apply these constraints to any UI work in this conversation.\n\n- `/baseline-ui <file>`\n  Review the file against all constraints below and output:\n  - violations (quote the exact line/snippet)\n  - why it matters (1 short sentence)\n  - a concrete fix (code-level suggestion)\n\n## Stack\n\n- MUST use Tailwind CSS defaults unless custom values already exist or are explicitly requested\n- MUST use `motion/react` (formerly `framer-motion`) when JavaScript animation is required\n- SHOULD use `tw-animate-css` for entrance and micro-animations in Tailwind CSS\n- MUST use `cn` utility (`clsx` + `tailwind-merge`) for class logic\n\n## Components\n\n- MUST use accessible component primitives for anything with keyboard or focus behavior (`Base UI`, `React Aria`, `Radix`)\n- MUST use the project’s existing component primitives first\n- NEVER mix primitive systems within the same interaction surface\n- SHOULD prefer [`Base UI`](https://base-ui.com/react/components) for new primitives if compatible with the stack\n- MUST add an `aria-label` to icon-only buttons\n- NEVER rebuild keyboard or focus behavior by hand unless explicitly requested\n\n## Interaction\n\n- MUST use an `AlertDialog` for destructive or irreversible actions\n- SHOULD use structural skeletons for loading states\n- NEVER use `h-screen`, use `h-dvh`\n- MUST respect `safe-area-inset` for fixed elements\n- MUST show errors next to where the action happens\n- NEVER block paste in `input` or `textarea` elements\n\n## Animation\n\n- NEVER add animation unless it is explicitly requested\n- MUST animate only compositor props (`transform`, `opacity`)\n- NEVER animate layout properties (`width`, `height`, `top`, `left`, `margin`, `padding`)\n- SHOULD avoid animating paint properties (`background`, `color`) except for small, local UI (text, icons)\n- SHOULD use `ease-out` on entrance\n- NEVER exceed `200ms` for interaction feedback\n- MUST pause looping animations when off-screen\n- SHOULD respect `prefers-reduced-motion`\n- NEVER introduce custom easing curves unless explicitly requested\n- SHOULD avoid animating large images or full-screen surfaces\n\n## Typography\n\n- MUST use `text-balance` for headings and `text-pretty` for body/paragraphs\n- MUST use `tabular-nums` for data\n- SHOULD use `truncate` or `line-clamp` for dense UI\n- NEVER modify `letter-spacing` (`tracking-*`) unless explicitly requested\n\n## Layout\n\n- MUST use a fixed `z-index` scale (no arbitrary `z-*`)\n- SHOULD use `size-*` for square elements instead of `w-*` + `h-*`\n\n## Performance\n\n- NEVER animate large `blur()` or `backdrop-filter` surfaces\n- NEVER apply `will-change` outside an active animation\n- NEVER use `useEffect` for anything that can be expressed as render logic\n\n## Design\n\n- NEVER use gradients unless explicitly requested\n- NEVER use purple or multicolor gradients\n- NEVER use glow effects as primary affordances\n- SHOULD use Tailwind CSS default shadow scale unless explicitly requested\n- MUST give empty states one clear next action\n- SHOULD limit accent color usage to one per view\n- SHOULD use existing theme or Tailwind CSS color tokens before introducing new ones\n\n<!-- File: .agents/skills/chat-sdk/SKILL.md (Tokens: ~2220 | Category: anthropic-skill) -->\n---\nname: chat-sdk\ndescription: >\n  Build multi-platform chat bots with Chat SDK (`chat` npm package). Use when developers want to\n  (1) Build a Slack, Teams, Google Chat, Discord, Telegram, GitHub, Linear, or WhatsApp bot,\n  (2) Use Chat SDK to handle mentions, direct messages, subscribed threads, reactions, slash\n  commands, cards, modals, files, or AI streaming,\n  (3) Set up webhook routes or multi-adapter bots,\n  (4) Send rich cards or streamed AI responses to chat platforms,\n  (5) Build or maintain a custom adapter or state adapter.\n  Triggers on \"chat sdk\", \"chat bot\", \"slack bot\", \"teams bot\", \"google chat bot\", \"discord bot\",\n  \"telegram bot\", \"whatsapp bot\", \"@chat-adapter\", \"@chat-adapter/state-\", \"custom adapter\",\n  \"state adapter\", \"build adapter\", and building bots that work across multiple chat platforms.\n---\n\n# Chat SDK\n\nUnified TypeScript SDK for building chat bots across Slack, Teams, Google Chat, Discord, Telegram, GitHub, Linear, and WhatsApp. Write bot logic once, deploy everywhere.\n\n## Start with published sources\n\nWhen Chat SDK is installed in a user project, inspect the published files that ship in `node_modules`:\n\n```\nnode_modules/chat/docs/                    # bundled docs\nnode_modules/chat/dist/index.d.ts          # core API types\nnode_modules/chat/dist/jsx-runtime.d.ts    # JSX runtime types\nnode_modules/chat/docs/contributing/       # adapter-authoring docs\nnode_modules/chat/docs/guides/             # framework/platform guides\n```\n\nIf one of the paths below does not exist, that package is not installed in the project yet.\n\nRead these before writing code:\n- `node_modules/chat/docs/getting-started.mdx` — install and setup\n- `node_modules/chat/docs/usage.mdx` — `Chat` config and lifecycle\n- `node_modules/chat/docs/handling-events.mdx` — event routing and handlers\n- `node_modules/chat/docs/threads-messages-channels.mdx` — thread/channel/message model\n- `node_modules/chat/docs/posting-messages.mdx` — post, edit, delete, schedule\n- `node_modules/chat/docs/streaming.mdx` — AI SDK integration and streaming semantics\n- `node_modules/chat/docs/cards.mdx` — JSX cards\n- `node_modules/chat/docs/actions.mdx` — button/select interactions\n- `node_modules/chat/docs/modals.mdx` — modal submit/close flows\n- `node_modules/chat/docs/slash-commands.mdx` — slash command routing\n- `node_modules/chat/docs/direct-messages.mdx` — DM behavior and `openDM()`\n- `node_modules/chat/docs/files.mdx` — attachments/uploads\n- `node_modules/chat/docs/state.mdx` — persistence, locking, dedupe\n- `node_modules/chat/docs/adapters.mdx` — cross-platform feature matrix\n- `node_modules/chat/docs/api/chat.mdx` — exact `Chat` API\n- `node_modules/chat/docs/api/thread.mdx` — exact `Thread` API\n- `node_modules/chat/docs/api/message.mdx` — exact `Message` API\n- `node_modules/chat/docs/api/modals.mdx` — modal element and event details\n\nFor the specific adapter or state package you are using, inspect that installed package's `dist/index.d.ts` export surface in `node_modules`.\n\n## Quick start\n\n```typescript\nimport { Chat } from \"chat\";\nimport { createSlackAdapter } from \"@chat-adapter/slack\";\nimport { createRedisState } from \"@chat-adapter/state-redis\";\n\nconst bot = new Chat({\n  userName: \"mybot\",\n  adapters: {\n    slack: createSlackAdapter(),\n  },\n  state: createRedisState(),\n  dedupeTtlMs: 600_000,\n});\n\nbot.onNewMention(async (thread) => {\n  await thread.subscribe();\n  await thread.post(\"Hello! I'm listening to this thread.\");\n});\n\nbot.onSubscribedMessage(async (thread, message) => {\n  await thread.post(`You said: ${message.text}`);\n});\n```\n\n## Core concepts\n\n- **Chat** — main entry point; coordinates adapters, routing, locks, and state\n- **Adapters** — platform-specific integrations for Slack, Teams, Google Chat, Discord, Telegram, GitHub, Linear, and WhatsApp\n- **State adapters** — persistence for subscriptions, locks, dedupe, and thread state\n- **Thread** — conversation context with `post()`, `stream()`, `subscribe()`, `setState()`, `startTyping()`\n- **Message** — normalized content with `text`, `formatted`, attachments, author info, and platform `raw`\n- **Channel** — container for threads and top-level posts\n\n## Event handlers\n\n| Handler | Trigger |\n|---------|---------|\n| `onNewMention` | Bot @-mentioned in an unsubscribed thread |\n| `onDirectMessage` | New DM in an unsubscribed DM thread |\n| `onSubscribedMessage` | Any message in a subscribed thread |\n| `onNewMessage(regex)` | Regex match in an unsubscribed thread |\n| `onReaction(emojis?)` | Emoji added or removed |\n| `onAction(actionIds?)` | Button clicks and select/radio interactions |\n| `onModalSubmit(callbackId?)` | Modal form submitted |\n| `onModalClose(callbackId?)` | Modal dismissed/cancelled |\n| `onSlashCommand(commands?)` | Slash command invocation |\n| `onAssistantThreadStarted` | Slack assistant thread opened |\n| `onAssistantContextChanged` | Slack assistant context changed |\n| `onAppHomeOpened` | Slack App Home opened |\n| `onMemberJoinedChannel` | Slack member joined channel event |\n\nRead `node_modules/chat/docs/handling-events.mdx`, `node_modules/chat/docs/actions.mdx`, `node_modules/chat/docs/modals.mdx`, and `node_modules/chat/docs/slash-commands.mdx` before wiring handlers. `onDirectMessage` behavior is documented in `node_modules/chat/docs/direct-messages.mdx`.\n\n## Streaming\n\nPass any `AsyncIterable<string>` to `thread.post()` or `thread.stream()`. For AI SDK, prefer `result.fullStream` over `result.textStream` when available so step boundaries are preserved.\n\n```typescript\nimport { ToolLoopAgent } from \"ai\";\n\nconst agent = new ToolLoopAgent({ model: \"anthropic/claude-4.5-sonnet\" });\n\nbot.onNewMention(async (thread, message) => {\n  const result = await agent.stream({ prompt: message.text });\n  await thread.post(result.fullStream);\n});\n```\n\nKey details:\n- `streamingUpdateIntervalMs` controls post+edit fallback cadence\n- `fallbackStreamingPlaceholderText` defaults to `\"...\"`; set `null` to disable\n- Structured `StreamChunk` support is Slack-only; other adapters ignore non-text chunks\n\n## Cards and modals (JSX)\n\nSet `jsxImportSource: \"chat\"` in `tsconfig.json`.\n\nCard components:\n- `Card`, `CardText`, `Section`, `Fields`, `Field`, `Button`, `CardLink`, `LinkButton`, `Actions`, `Select`, `SelectOption`, `RadioSelect`, `Table`, `Image`, `Divider`\n\nModal components:\n- `Modal`, `TextInput`, `Select`, `SelectOption`, `RadioSelect`\n\n```tsx\nawait thread.post(\n  <Card title=\"Order #1234\">\n    <CardText>Your order has been received.</CardText>\n    <Actions>\n      <Button id=\"approve\" style=\"primary\">Approve</Button>\n      <Button id=\"reject\" style=\"danger\">Reject</Button>\n    </Actions>\n  </Card>\n);\n```\n\n## Adapter inventory\n\n### Official platform adapters\n\n| Platform | Package | Factory |\n|---------|---------|---------|\n| Slack | `@chat-adapter/slack` | `createSlackAdapter` |\n| Microsoft Teams | `@chat-adapter/teams` | `createTeamsAdapter` |\n| Google Chat | `@chat-adapter/gchat` | `createGoogleChatAdapter` |\n| Discord | `@chat-adapter/discord` | `createDiscordAdapter` |\n| GitHub | `@chat-adapter/github` | `createGitHubAdapter` |\n| Linear | `@chat-adapter/linear` | `createLinearAdapter` |\n| Telegram | `@chat-adapter/telegram` | `createTelegramAdapter` |\n| WhatsApp Business Cloud | `@chat-adapter/whatsapp` | `createWhatsAppAdapter` |\n\n### Official state adapters\n\n| State backend | Package | Factory |\n|--------------|---------|---------|\n| Redis | `@chat-adapter/state-redis` | `createRedisState` |\n| ioredis | `@chat-adapter/state-ioredis` | `createIoRedisState` |\n| PostgreSQL | `@chat-adapter/state-pg` | `createPostgresState` |\n| Memory | `@chat-adapter/state-memory` | `createMemoryState` |\n\n### Community adapters\n\n- `chat-state-cloudflare-do`\n- `@beeper/chat-adapter-matrix`\n- `chat-adapter-imessage`\n- `@bitbasti/chat-adapter-webex`\n- `@resend/chat-sdk-adapter`\n- `chat-adapter-baileys`\n\n### Coming-soon platform entries\n\n- Instagram\n- Signal\n- X\n- Messenger\n\n## Building a custom adapter\n\nRead these published docs first:\n- `node_modules/chat/docs/contributing/building.mdx`\n- `node_modules/chat/docs/contributing/testing.mdx`\n- `node_modules/chat/docs/contributing/publishing.mdx`\n\nAlso inspect:\n- `node_modules/chat/dist/index.d.ts` — `Adapter` and related interfaces\n- `node_modules/@chat-adapter/shared/dist/index.d.ts` — shared errors and utilities\n- Installed official adapter `dist/index.d.ts` files — reference implementations for config and APIs\n\nA custom adapter needs request verification, webhook parsing, message/thread/channel operations, ID encoding/decoding, and a format converter. Use `BaseFormatConverter` from `chat` and shared utilities from `@chat-adapter/shared`.\n\n## Webhook setup\n\nEach registered adapter exposes `bot.webhooks.<name>`. Wire those directly to your HTTP framework routes. See `node_modules/chat/docs/guides/slack-nextjs.mdx` and `node_modules/chat/docs/guides/discord-nuxt.mdx` for framework-specific route patterns.\n\n<!-- File: .agents/skills/code-review/SKILL.md (Tokens: ~1198 | Category: anthropic-skill) -->\n---\nname: code-review\ndescription: Reviews code changes and provides actionable feedback. Use when the user asks to review a PR, diff, commit, or code changes. Triggers on \"/review\", \"review this PR\", \"review my changes\", \"code review\".\n---\n\nYou are a code reviewer. Your job is to review code changes and provide actionable feedback.\n\n## Determining What to Review\n\nBased on the input provided, determine which type of review to perform:\n\n1. **No arguments (default)**: Review all uncommitted changes\n   - Run: `git diff` for unstaged changes\n   - Run: `git diff --cached` for staged changes\n\n2. **Commit hash** (40-char SHA or short hash): Review that specific commit\n   - Run: `git show $ARGUMENTS`\n\n3. **Branch name**: Compare current branch to the specified branch\n   - Run: `git diff $ARGUMENTS...HEAD`\n\n4. **PR URL or number** (contains \"github.com\" or \"pull\" or looks like a PR number): Review the pull request\n   - Run: `gh pr view $ARGUMENTS` to get PR context\n   - Run: `gh pr diff $ARGUMENTS` to get the diff\n\nUse best judgement when processing input.\n\n---\n\n## Gathering Context\n\n**Diffs alone are not enough.** After getting the diff, read the entire file(s) being modified to understand the full context. Code that looks wrong in isolation may be correct given surrounding logic—and vice versa.\n\n- Use the diff to identify which files changed\n- Read the full file to understand existing patterns, control flow, and error handling\n- When changes touch inputs, auth, storage, networking, rendering, or secrets, trace the trust boundary instead of reviewing the code in isolation\n- Check for existing style guide or conventions files (CONVENTIONS.md, AGENTS.md, .editorconfig, etc.)\n\n---\n\n## What to Look For\n\n**Bugs** - Your primary focus.\n- Logic errors, off-by-one mistakes, incorrect conditionals\n- If-else guards: missing guards, incorrect branching, unreachable code paths\n- Edge cases: null/empty/undefined inputs, error conditions, race conditions\n- Broken error handling that swallows failures, throws unexpectedly or returns error types that are not caught.\n\n**Security / Safety** - Treat this as a first-class review concern, not an afterthought.\n- Assume changed code may be reachable by untrusted users or hostile input unless you can verify otherwise\n- Look for injection, XSS, auth/authz bypass, CSRF, SSRF, open redirects, path traversal, unsafe file access, secret/token exposure, privilege escalation, insecure defaults, and tenant/data isolation leaks\n- Check that validation and authorization happen at the real boundary, not only in the UI or caller\n- Verify sensitive operations fail closed, do not log secrets, and do not expand access beyond the intended actor/resource scope\n- Prefer flagging realistic exploit paths over generic \"security concern\" comments; explain the attacker-controlled input, boundary, and impact\n\n**Structure** - Does the code fit the codebase?\n- Does it follow existing patterns and conventions?\n- Are there established abstractions it should use but doesn't?\n- Excessive nesting that could be flattened with early returns or extraction\n\n**Performance** - Only flag if obviously problematic.\n- O(n²) on unbounded data, N+1 queries, blocking I/O on hot paths\n\n---\n\n## Before You Flag Something\n\n**Be certain.** If you're going to call something a bug, you need to be confident it actually is one.\n\n- Only review the changes - do not review pre-existing code that wasn't modified\n- Don't flag something as a bug if you're unsure - investigate first\n- Don't invent hypothetical problems - if an edge case matters, explain the realistic scenario where it breaks\n- For security findings, describe the concrete exploit path or trust-boundary failure instead of vague risk language\n- If you need more context to be sure, use the tools below to get it\n\n**Don't be a zealot about style.** When checking code against conventions:\n\n- Verify the code is *actually* in violation. Don't complain about else statements if early returns are already being used correctly.\n- Some \"violations\" are acceptable when they're the simplest option. A `let` statement is fine if the alternative is convoluted.\n- Excessive nesting is a legitimate concern regardless of other style choices.\n- Don't flag style preferences as issues unless they clearly violate established project conventions.\n\n---\n\n## Tools\n\nUse these to inform your review:\n\n- **Explore agent** - Find how existing code handles similar problems. Check patterns, conventions, and prior art before claiming something doesn't fit.\n\nIf you're uncertain about something and can't verify it with these tools, say \"I'm not sure about X\" rather than flagging it as a definite issue.\n\n---\n\n## Output\n\n1. If there is a bug, be direct and clear about why it is a bug.\n2. Clearly communicate severity of issues. Do not overstate severity.\n3. Critiques should clearly and explicitly communicate the scenarios, environments, or inputs that are necessary for the bug to arise. The comment should immediately indicate that the issue's severity depends on these factors.\n4. For security findings, explicitly state the attacker-controlled input, missing control, and concrete impact.\n5. Your tone should be matter-of-fact and not accusatory or overly positive. It should read as a helpful AI assistant suggestion without sounding too much like a human reviewer.\n6. Write so the reader can quickly understand the issue without reading too closely.\n7. AVOID flattery, do not give any comments that are not helpful to the reader. Avoid phrasing like \"Great job ...\", \"Thanks for ...\".\n\n<!-- File: .agents/skills/deploy-open-harness/SKILL.md (Tokens: ~1606 | Category: anthropic-skill) -->\n---\nname: deploy-open-harness\ndescription: Guides a user through collecting the credentials needed to deploy their own copy of Open Harness, deploying this repo on Vercel, and completing first-run setup. Use for requests about deploying, self-hosting, configuring credentials, or getting started with a fork of this app.\n---\n\nYou are helping a user deploy their own copy of Open Harness.\n\nBase your guidance on the current codebase, not on older Harness-era setup assumptions.\n\n## First rule: verify current requirements from the repo\n\nBefore giving deployment advice, read these files if you have not already:\n\n- `README.md`\n- `apps/web/.env.example`\n- `apps/web/lib/db/client.ts`\n- `apps/web/lib/jwe/encrypt.ts`\n- `apps/web/lib/crypto.ts`\n- `apps/web/app/api/auth/signin/vercel/route.ts`\n- `apps/web/app/api/auth/vercel/callback/route.ts`\n- `apps/web/app/api/github/app/install/route.ts`\n- `apps/web/app/api/github/app/callback/route.ts`\n- `apps/web/lib/github/app-auth.ts`\n- `apps/web/lib/redis.ts`\n- `apps/web/lib/sandbox/config.ts`\n\nIf the code and the docs disagree, trust the code and say so.\n\nDo not rely on `scripts/setup.sh`.\n\n## Goals\n\nHelp the user:\n\n1. Decide whether they want a minimal deploy or the full GitHub-enabled coding-agent flow.\n2. Collect only the credentials actually required for that scope.\n3. Understand where to obtain each credential.\n4. Deploy this repo on Vercel.\n5. Complete first-run verification.\n6. Leave with a short next-steps checklist.\n\n## Safety rules\n\n- Never ask the user to paste secrets into chat.\n- Tell them where each value belongs, but keep secret values in Vercel project env vars or local env files.\n- Separate blockers for a minimal deploy from blockers for the full GitHub-enabled flow.\n- Be explicit when something is optional.\n\n## Scope the deployment first\n\nStart by determining which path the user wants:\n\n### 1) Minimal deploy\nA working hosted app where the user can deploy it, sign in with Vercel, and use the product without GitHub repo access.\n\n### 2) Full deploy\nEverything in the minimal deploy, plus GitHub account linking, GitHub App installation, private repo access, pushes, and PR creation.\n\nIf the user is unsure, recommend **minimal deploy first**, then layer on GitHub.\n\n## Credential checklist\n\nUse this checklist when guiding the user.\n\n### Required for the app to run\n\n- `POSTGRES_URL`\n- `JWE_SECRET`\n\n### Required for a usable hosted deployment\n\n- `ENCRYPTION_KEY`\n- `NEXT_PUBLIC_VERCEL_APP_CLIENT_ID`\n- `VERCEL_APP_CLIENT_SECRET`\n\n### Required for GitHub-enabled repo flows\n\n- `NEXT_PUBLIC_GITHUB_CLIENT_ID`\n- `GITHUB_CLIENT_SECRET`\n- `GITHUB_APP_ID`\n- `GITHUB_APP_PRIVATE_KEY`\n- `NEXT_PUBLIC_GITHUB_APP_SLUG`\n- `GITHUB_WEBHOOK_SECRET`\n\n### Optional\n\n- `REDIS_URL` or `KV_URL`\n- `VERCEL_PROJECT_PRODUCTION_URL`\n- `NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL`\n- `VERCEL_SANDBOX_BASE_SNAPSHOT_ID`\n- `ELEVENLABS_API_KEY`\n\n## How to explain each credential\n\n### PostgreSQL\nTell the user to create a Postgres database and copy the connection string into `POSTGRES_URL`.\n\n### JWE secret\nExplain that this is required for session encryption.\n\nRecommended generation command:\n\n```bash\nopenssl rand -base64 32 | tr '+/' '-_' | tr -d '=\\n'\n```\n\n### Encryption key\nExplain that provider tokens are encrypted at rest and the value must be a 64-character hex string.\n\nRecommended generation command:\n\n```bash\nopenssl rand -hex 32\n```\n\n### Vercel OAuth app\nTell the user to create a Vercel OAuth app and set:\n\n- Callback URL: `https://YOUR_DOMAIN/api/auth/vercel/callback`\n- For local dev: `http://localhost:3000/api/auth/vercel/callback`\n\nStore the credentials as:\n\n- `NEXT_PUBLIC_VERCEL_APP_CLIENT_ID`\n- `VERCEL_APP_CLIENT_SECRET`\n\n### GitHub App\nTell the user they do not need a separate GitHub OAuth app. Open Harness uses the GitHub App's user authorization flow.\n\nTell the user to create a GitHub App and set:\n\n- Homepage URL: `https://YOUR_DOMAIN`\n- Callback URL: `https://YOUR_DOMAIN/api/github/app/callback`\n- Setup URL: `https://YOUR_DOMAIN/api/github/app/callback`\n- For local dev: homepage `http://localhost:3000`, callback/setup `http://localhost:3000/api/github/app/callback`\n\nAlso tell them to:\n\n- enable \"Request user authorization (OAuth) during installation\"\n- use the GitHub App Client ID and Client Secret for `NEXT_PUBLIC_GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET`\n- make the app public if they want org installs to work cleanly\n- generate a webhook secret\n- download/generate the private key\n\nStore the values as:\n\n- `NEXT_PUBLIC_GITHUB_CLIENT_ID`\n- `GITHUB_CLIENT_SECRET`\n- `GITHUB_APP_ID`\n- `GITHUB_APP_PRIVATE_KEY`\n- `NEXT_PUBLIC_GITHUB_APP_SLUG`\n- `GITHUB_WEBHOOK_SECRET`\n\nMention that `GITHUB_APP_PRIVATE_KEY` can be stored either as PEM contents with escaped newlines or as a base64-encoded PEM.\n\n### Redis / KV\nExplain that Redis is optional. It improves resumable streams, stop signaling, and caching, but it is not required for the first deploy.\n\n## Deployment flow\n\nGuide the user through this sequence:\n\n1. Fork the repo.\n2. Import it into Vercel at the repo root.\n3. Add the baseline env vars:\n   - `POSTGRES_URL`\n   - `JWE_SECRET`\n   - `ENCRYPTION_KEY`\n4. Deploy once to get a stable production URL.\n5. Create the Vercel OAuth app using that production URL.\n6. Add `NEXT_PUBLIC_VERCEL_APP_CLIENT_ID` and `VERCEL_APP_CLIENT_SECRET`.\n7. Redeploy.\n8. If the user wants the full GitHub flow, create the GitHub App using the production URL, add the GitHub env vars, and redeploy again.\n9. Optionally add Redis/KV and the production URL vars.\n\nIf the user already has a custom domain ready, it is fine to use that domain from the start instead of the default `vercel.app` production URL.\n\n## First-run verification\n\nFor a minimal deploy, walk the user through:\n\n1. Open the production site.\n2. Sign in with Vercel.\n3. Confirm they land in the app successfully.\n4. Create a session and confirm the basic UI loads.\n\nFor the full deploy, also verify:\n\n1. GitHub account linking works.\n2. GitHub App installation completes.\n3. Installations or repos appear in the UI.\n4. A repo-backed session can start.\n5. The sandbox starts and the agent can work in the repo.\n\nIf something fails, identify the missing credential or callback mismatch instead of giving generic advice.\n\n## Response format\n\nWhen helping a user, prefer this structure:\n\n1. **Target scope** — minimal or full.\n2. **Credential checklist** — grouped into required now vs optional later.\n3. **How to get each missing credential** — short, concrete instructions.\n4. **Deploy steps** — only the next actions the user should take.\n5. **Verification** — what to click/test after deploy.\n6. **Next upgrades** — Redis, GitHub, voice, custom domain, snapshot override, only if relevant.\n\nBe concise. Keep the user moving toward the next unblocker.\n\n<!-- File: .agents/skills/emil-design-eng/SKILL.md (Tokens: ~6034 | Category: anthropic-skill) -->\n---\nname: emil-design-eng\ndescription: This skill encodes Emil Kowalski's philosophy on UI polish, component design, animation decisions, and the invisible details that make software feel great.\n---\n\n# Design Engineering\n\nYou are a design engineer with the craft sensibility. You build interfaces where every detail compounds into something that feels right. You understand that in a world where everyone's software is good enough, taste is the differentiator.\n\n## Core Philosophy\n\n### Taste is trained, not innate\n\nGood taste is not personal preference. It is a trained instinct: the ability to see beyond the obvious and recognize what elevates. You develop it by surrounding yourself with great work, thinking deeply about why something feels good, and practicing relentlessly.\n\nWhen building UI, don't just make it work. Study why the best interfaces feel the way they do. Reverse engineer animations. Inspect interactions. Be curious.\n\n### Unseen details compound\n\nMost details users never consciously notice. That is the point. When a feature functions exactly as someone assumes it should, they proceed without giving it a second thought. That is the goal.\n\n> \"All those unseen details combine to produce something that's just stunning, like a thousand barely audible voices all singing in tune.\" - Paul Graham\n\nEvery decision below exists because the aggregate of invisible correctness creates interfaces people love without knowing why.\n\n### Beauty is leverage\n\nPeople select tools based on the overall experience, not just functionality. Good defaults and good animations are real differentiators. Beauty is underutilized in software. Use it as leverage to stand out.\n\n## Review Format (Required)\n\nWhen reviewing UI code, you MUST use a markdown table with Before/After columns. Do NOT use a list with \"Before:\" and \"After:\" on separate lines. Always output an actual markdown table like this:\n\n| Before                                | After                                                             | Why                                                                          |\n| ------------------------------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------- |\n| `transition: all 300ms`               | `transition: transform 200ms ease-out`                            | Specify exact properties; avoid `all`                                        |\n| `transform: scale(0)`                 | `transform: scale(0.95); opacity: 0`                              | Nothing in the real world appears from nothing                               |\n| `ease-in` on dropdown                 | `ease-out` with custom curve                                      | `ease-in` feels sluggish; `ease-out` gives instant feedback                  |\n| No `:active` state on button          | `transform: scale(0.97)` on `:active`                             | Buttons must feel responsive to press                                        |\n| `transform-origin: center` on popover | `transform-origin: var(--radix-popover-content-transform-origin)` | Popovers should scale from their trigger (not modals — modals stay centered) |\n\nWrong format (never do this):\n\n```\nBefore: transition: all 300ms\nAfter: transition: transform 200ms ease-out\n────────────────────────────\nBefore: scale(0)\nAfter: scale(0.95)\n```\n\nCorrect format: A single markdown table with | Before | After | Why | columns, one row per issue found. The \"Why\" column briefly explains the reasoning.\n\n## The Animation Decision Framework\n\nBefore writing any animation code, answer these questions in order:\n\n### 1. Should this animate at all?\n\n**Ask:** How often will users see this animation?\n\n| Frequency                                                   | Decision                     |\n| ----------------------------------------------------------- | ---------------------------- |\n| 100+ times/day (keyboard shortcuts, command palette toggle) | No animation. Ever.          |\n| Tens of times/day (hover effects, list navigation)          | Remove or drastically reduce |\n| Occasional (modals, drawers, toasts)                        | Standard animation           |\n| Rare/first-time (onboarding, feedback forms, celebrations)  | Can add delight              |\n\n**Never animate keyboard-initiated actions.** These actions are repeated hundreds of times daily. Animation makes them feel slow, delayed, and disconnected from the user's actions.\n\nRaycast has no open/close animation. That is the optimal experience for something used hundreds of times a day.\n\n### 2. What is the purpose?\n\nEvery animation must have a clear answer to \"why does this animate?\"\n\nValid purposes:\n\n- **Spatial consistency**: toast enters and exits from the same direction, making swipe-to-dismiss feel intuitive\n- **State indication**: a morphing feedback button shows the state change\n- **Explanation**: a marketing animation that shows how a feature works\n- **Feedback**: a button scales down on press, confirming the interface heard the user\n- **Preventing jarring changes**: elements appearing or disappearing without transition feel broken\n\nIf the purpose is just \"it looks cool\" and the user will see it often, don't animate.\n\n### 3. What easing should it use?\n\nIs the element entering or exiting?\nYes → ease-out (starts fast, feels responsive)\nNo →\nIs it moving/morphing on screen?\nYes → ease-in-out (natural acceleration/deceleration)\nIs it a hover/color change?\nYes → ease\nIs it constant motion (marquee, progress bar)?\nYes → linear\nDefault → ease-out\n\n**Critical: use custom easing curves.** The built-in CSS easings are too weak. They lack the punch that makes animations feel intentional.\n\n```css\n/* Strong ease-out for UI interactions */\n--ease-out: cubic-bezier(0.23, 1, 0.32, 1);\n\n/* Strong ease-in-out for on-screen movement */\n--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);\n\n/* iOS-like drawer curve (from Ionic Framework) */\n--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);\n```\n\n**Never use ease-in for UI animations.** It starts slow, which makes the interface feel sluggish and unresponsive. A dropdown with `ease-in` at 300ms _feels_ slower than `ease-out` at the same 300ms, because ease-in delays the initial movement — the exact moment the user is watching most closely.\n\n**Easing curve resources:** Don't create curves from scratch. Use [easing.dev](https://easing.dev/) or [easings.co](https://easings.co/) to find stronger custom variants of standard easings.\n\n### 4. How fast should it be?\n\n| Element                  | Duration      |\n| ------------------------ | ------------- |\n| Button press feedback    | 100-160ms     |\n| Tooltips, small popovers | 125-200ms     |\n| Dropdowns, selects       | 150-250ms     |\n| Modals, drawers          | 200-500ms     |\n| Marketing/explanatory    | Can be longer |\n\n**Rule: UI animations should stay under 300ms.** A 180ms dropdown feels more responsive than a 400ms one. A faster-spinning spinner makes the app feel like it loads faster, even when the load time is identical.\n\n### Perceived performance\n\nSpeed in animation is not just about feeling snappy — it directly affects how users perceive your app's performance:\n\n- A **fast-spinning spinner** makes loading feel faster (same load time, different perception)\n- A **180ms select** animation feels more responsive than a **400ms** one\n- **Instant tooltips** after the first one is open (skip delay + skip animation) make the whole toolbar feel faster\n\nThe perception of speed matters as much as actual speed. Easing amplifies this: `ease-out` at 200ms _feels_ faster than `ease-in` at 200ms because the user sees immediate movement.\n\n## Spring Animations\n\nSprings feel more natural than duration-based animations because they simulate real physics. They don't have fixed durations — they settle based on physical parameters.\n\n### When to use springs\n\n- Drag interactions with momentum\n- Elements that should feel \"alive\" (like Apple's Dynamic Island)\n- Gestures that can be interrupted mid-animation\n- Decorative mouse-tracking interactions\n\n### Spring-based mouse interactions\n\nTying visual changes directly to mouse position feels artificial because it lacks motion. Use `useSpring` from Motion (formerly Framer Motion) to interpolate value changes with spring-like behavior instead of updating immediately.\n\n```jsx\nimport { useSpring } from \"framer-motion\";\n\n// Without spring: feels artificial, instant\nconst rotation = mouseX * 0.1;\n\n// With spring: feels natural, has momentum\nconst springRotation = useSpring(mouseX * 0.1, {\n  stiffness: 100,\n  damping: 10,\n});\n```\n\nThis works because the animation is **decorative** — it doesn't serve a function. If this were a functional graph in a banking app, no animation would be better. Know when decoration helps and when it hinders.\n\n### Spring configuration\n\n**Apple's approach (recommended — easier to reason about):**\n\n```js\n{ type: \"spring\", duration: 0.5, bounce: 0.2 }\n```\n\n**Traditional physics (more control):**\n\n```js\n{ type: \"spring\", mass: 1, stiffness: 100, damping: 10 }\n```\n\nKeep bounce subtle (0.1-0.3) when used. Avoid bounce in most UI contexts. Use it for drag-to-dismiss and playful interactions.\n\n### Interruptibility advantage\n\nSprings maintain velocity when interrupted — CSS animations and keyframes restart from zero. This makes springs ideal for gestures users might change mid-motion. When you click an expanded item and quickly press Escape, a spring-based animation smoothly reverses from its current position.\n\n## Component Building Principles\n\n### Buttons must feel responsive\n\nAdd `transform: scale(0.97)` on `:active`. This gives instant feedback, making the UI feel like it is truly listening to the user.\n\n```css\n.button {\n  transition: transform 160ms ease-out;\n}\n\n.button:active {\n  transform: scale(0.97);\n}\n```\n\nThis applies to any pressable element. The scale should be subtle (0.95-0.98).\n\n### Never animate from scale(0)\n\nNothing in the real world disappears and reappears completely. Elements animating from `scale(0)` look like they come out of nowhere.\n\nStart from `scale(0.9)` or higher, combined with opacity. Even a barely-visible initial scale makes the entrance feel more natural, like a balloon that has a visible shape even when deflated.\n\n```css\n/* Bad */\n.entering {\n  transform: scale(0);\n}\n\n/* Good */\n.entering {\n  transform: scale(0.95);\n  opacity: 0;\n}\n```\n\n### Make popovers origin-aware\n\nPopovers should scale in from their trigger, not from center. The default `transform-origin: center` is wrong for almost every popover. **Exception: modals.** Modals should keep `transform-origin: center` because they are not anchored to a specific trigger — they appear centered in the viewport.\n\n```css\n/* Radix UI */\n.popover {\n  transform-origin: var(--radix-popover-content-transform-origin);\n}\n\n/* Base UI */\n.popover {\n  transform-origin: var(--transform-origin);\n}\n```\n\nWhether the user notices the difference individually does not matter. In the aggregate, unseen details become visible. They compound.\n\n### Tooltips: skip delay on subsequent hovers\n\nTooltips should delay before appearing to prevent accidental activation. But once one tooltip is open, hovering over adjacent tooltips should open them instantly with no animation. This feels faster without defeating the purpose of the initial delay.\n\n```css\n.tooltip {\n  transition:\n    transform 125ms ease-out,\n    opacity 125ms ease-out;\n  transform-origin: var(--transform-origin);\n}\n\n.tooltip[data-starting-style],\n.tooltip[data-ending-style] {\n  opacity: 0;\n  transform: scale(0.97);\n}\n\n/* Skip animation on subsequent tooltips */\n.tooltip[data-instant] {\n  transition-duration: 0ms;\n}\n```\n\n### Use CSS transitions over keyframes for interruptible UI\n\nCSS transitions can be interrupted and retargeted mid-animation. Keyframes restart from zero. For any interaction that can be triggered rapidly (adding toasts, toggling states), transitions produce smoother results.\n\n```css\n/* Interruptible - good for UI */\n.toast {\n  transition: transform 400ms ease;\n}\n\n/* Not interruptible - avoid for dynamic UI */\n@keyframes slideIn {\n  from {\n    transform: translateY(100%);\n  }\n  to {\n    transform: translateY(0);\n  }\n}\n```\n\n### Use blur to mask imperfect transitions\n\nWhen a crossfade between two states feels off despite trying different easings and durations, add subtle `filter: blur(2px)` during the transition.\n\n**Why blur works:** Without blur, you see two distinct objects during a crossfade — the old state and the new state overlapping. This looks unnatural. Blur bridges the visual gap by blending the two states together, tricking the eye into perceiving a single smooth transformation instead of two objects swapping.\n\nCombine blur with scale-on-press (`scale(0.97)`) for a polished button state transition:\n\n```css\n.button {\n  transition: transform 160ms ease-out;\n}\n\n.button:active {\n  transform: scale(0.97);\n}\n\n.button-content {\n  transition:\n    filter 200ms ease,\n    opacity 200ms ease;\n}\n\n.button-content.transitioning {\n  filter: blur(2px);\n  opacity: 0.7;\n}\n```\n\nKeep blur under 20px. Heavy blur is expensive, especially in Safari.\n\n### Animate enter states with @starting-style\n\nThe modern CSS way to animate element entry without JavaScript:\n\n```css\n.toast {\n  opacity: 1;\n  transform: translateY(0);\n  transition:\n    opacity 400ms ease,\n    transform 400ms ease;\n\n  @starting-style {\n    opacity: 0;\n    transform: translateY(100%);\n  }\n}\n```\n\nThis replaces the common React pattern of using `useEffect` to set `mounted: true` after initial render. Use `@starting-style` when browser support allows; fall back to the `data-mounted` attribute pattern otherwise.\n\n```jsx\n// Legacy pattern (still works everywhere)\nuseEffect(() => {\n  setMounted(true);\n}, []);\n// <div data-mounted={mounted}>\n```\n\n## CSS Transform Mastery\n\n### translateY with percentages\n\nPercentage values in `translate()` are relative to the element's own size. Use `translateY(100%)` to move an element by its own height, regardless of actual dimensions. This is how Sonner positions toasts and how Vaul hides the drawer before animating in.\n\n```css\n/* Works regardless of drawer height */\n.drawer-hidden {\n  transform: translateY(100%);\n}\n\n/* Works regardless of toast height */\n.toast-enter {\n  transform: translateY(-100%);\n}\n```\n\nPrefer percentages over hardcoded pixel values. They are less error-prone and adapt to content.\n\n### scale() scales children too\n\nUnlike `width`/`height`, `scale()` also scales an element's children. When scaling a button on press, the font size, icons, and content scale proportionally. This is a feature, not a bug.\n\n### 3D transforms for depth\n\n`rotateX()`, `rotateY()` with `transform-style: preserve-3d` create real 3D effects in CSS. Orbiting animations, coin flips, and depth effects are all possible without JavaScript.\n\n```css\n.wrapper {\n  transform-style: preserve-3d;\n}\n\n@keyframes orbit {\n  from {\n    transform: translate(-50%, -50%) rotateY(0deg) translateZ(72px)\n      rotateY(360deg);\n  }\n  to {\n    transform: translate(-50%, -50%) rotateY(360deg) translateZ(72px)\n      rotateY(0deg);\n  }\n}\n```\n\n### transform-origin\n\nEvery element has an anchor point from which transforms execute. The default is center. Set it to match where the trigger lives for origin-aware interactions.\n\n## clip-path for Animation\n\n`clip-path` is not just for shapes. It is one of the most powerful animation tools in CSS.\n\n### The inset shape\n\n`clip-path: inset(top right bottom left)` defines a rectangular clipping region. Each value \"eats\" into the element from that side.\n\n```css\n/* Fully hidden from right */\n.hidden {\n  clip-path: inset(0 100% 0 0);\n}\n\n/* Fully visible */\n.visible {\n  clip-path: inset(0 0 0 0);\n}\n\n/* Reveal from left to right */\n.overlay {\n  clip-path: inset(0 100% 0 0);\n  transition: clip-path 200ms ease-out;\n}\n.button:active .overlay {\n  clip-path: inset(0 0 0 0);\n  transition: clip-path 2s linear;\n}\n```\n\n### Tabs with perfect color transitions\n\nDuplicate the tab list. Style the copy as \"active\" (different background, different text color). Clip the copy so only the active tab is visible. Animate the clip on tab change. This creates a seamless color transition that timing individual color transitions can never achieve.\n\n### Hold-to-delete pattern\n\nUse `clip-path: inset(0 100% 0 0)` on a colored overlay. On `:active`, transition to `inset(0 0 0 0)` over 2s with linear timing. On release, snap back with 200ms ease-out. Add `scale(0.97)` on the button for press feedback.\n\n### Image reveals on scroll\n\nStart with `clip-path: inset(0 0 100% 0)` (hidden from bottom). Animate to `inset(0 0 0 0)` when the element enters the viewport. Use `IntersectionObserver` or Framer Motion's `useInView` with `{ once: true, margin: \"-100px\" }`.\n\n### Comparison sliders\n\nOverlay two images. Clip the top one with `clip-path: inset(0 50% 0 0)`. Adjust the right inset value based on drag position. No extra DOM elements needed, fully hardware-accelerated.\n\n## Gesture and Drag Interactions\n\n### Momentum-based dismissal\n\nDon't require dragging past a threshold. Calculate velocity: `Math.abs(dragDistance) / elapsedTime`. If velocity exceeds ~0.11, dismiss regardless of distance. A quick flick should be enough.\n\n```js\nconst timeTaken = new Date().getTime() - dragStartTime.current.getTime();\nconst velocity = Math.abs(swipeAmount) / timeTaken;\n\nif (Math.abs(swipeAmount) >= SWIPE_THRESHOLD || velocity > 0.11) {\n  dismiss();\n}\n```\n\n### Damping at boundaries\n\nWhen a user drags past the natural boundary (e.g., dragging a drawer up when already at top), apply damping. The more they drag, the less the element moves. Things in real life don't suddenly stop; they slow down first.\n\n### Pointer capture for drag\n\nOnce dragging starts, set the element to capture all pointer events. This ensures dragging continues even if the pointer leaves the element bounds.\n\n### Multi-touch protection\n\nIgnore additional touch points after the initial drag begins. Without this, switching fingers mid-drag causes the element to jump to the new position.\n\n```js\nfunction onPress() {\n  if (isDragging) return;\n  // Start drag...\n}\n```\n\n### Friction instead of hard stops\n\nInstead of preventing upward drag entirely, allow it with increasing friction. It feels more natural than hitting an invisible wall.\n\n## Performance Rules\n\n### Only animate transform and opacity\n\nThese properties skip layout and paint, running on the GPU. Animating `padding`, `margin`, `height`, or `width` triggers all three rendering steps.\n\n### CSS variables are inheritable\n\nChanging a CSS variable on a parent recalculates styles for all children. In a drawer with many items, updating `--swipe-amount` on the container causes expensive style recalculation. Update `transform` directly on the element instead.\n\n```js\n// Bad: triggers recalc on all children\nelement.style.setProperty(\"--swipe-amount\", `${distance}px`);\n\n// Good: only affects this element\nelement.style.transform = `translateY(${distance}px)`;\n```\n\n### Framer Motion hardware acceleration caveat\n\nFramer Motion's shorthand properties (`x`, `y`, `scale`) are NOT hardware-accelerated. They use `requestAnimationFrame` on the main thread. For hardware acceleration, use the full `transform` string:\n\n```jsx\n// NOT hardware accelerated (convenient but drops frames under load)\n<motion.div animate={{ x: 100 }} />\n\n// Hardware accelerated (stays smooth even when main thread is busy)\n<motion.div animate={{ transform: \"translateX(100px)\" }} />\n```\n\nThis matters when the browser is simultaneously loading content, running scripts, or painting. At Vercel, the dashboard tab animation used Shared Layout Animations and dropped frames during page loads. Switching to CSS animations (off main thread) fixed it.\n\n### CSS animations beat JS under load\n\nCSS animations run off the main thread. When the browser is busy loading a new page, Framer Motion animations (using `requestAnimationFrame`) drop frames. CSS animations remain smooth. Use CSS for predetermined animations; JS for dynamic, interruptible ones.\n\n### Use WAAPI for programmatic CSS animations\n\nThe Web Animations API gives you JavaScript control with CSS performance. Hardware-accelerated, interruptible, and no library needed.\n\n```js\nelement.animate(\n  [{ clipPath: \"inset(0 0 100% 0)\" }, { clipPath: \"inset(0 0 0 0)\" }],\n  {\n    duration: 1000,\n    fill: \"forwards\",\n    easing: \"cubic-bezier(0.77, 0, 0.175, 1)\",\n  },\n);\n```\n\n## Accessibility\n\n### prefers-reduced-motion\n\nAnimations can cause motion sickness. Reduced motion means fewer and gentler animations, not zero. Keep opacity and color transitions that aid comprehension. Remove movement and position animations.\n\n```css\n@media (prefers-reduced-motion: reduce) {\n  .element {\n    animation: fade 0.2s ease;\n    /* No transform-based motion */\n  }\n}\n```\n\n```jsx\nconst shouldReduceMotion = useReducedMotion();\nconst closedX = shouldReduceMotion ? 0 : \"-100%\";\n```\n\n### Touch device hover states\n\n```css\n@media (hover: hover) and (pointer: fine) {\n  .element:hover {\n    transform: scale(1.05);\n  }\n}\n```\n\nTouch devices trigger hover on tap, causing false positives. Gate hover animations behind this media query.\n\n## The Sonner Principles (Building Loved Components)\n\nThese principles come from building Sonner (13M+ weekly npm downloads) and apply to any component:\n\n1. **Developer experience is key.** No hooks, no context, no complex setup. Insert `<Toaster />` once, call `toast()` from anywhere. The less friction to adopt, the more people will use it.\n\n2. **Good defaults matter more than options.** Ship beautiful out of the box. Most users never customize. The default easing, timing, and visual design should be excellent.\n\n3. **Naming creates identity.** \"Sonner\" (French for \"to ring\") feels more elegant than \"react-toast\". Sacrifice discoverability for memorability when appropriate.\n\n4. **Handle edge cases invisibly.** Pause toast timers when the tab is hidden. Fill gaps between stacked toasts with pseudo-elements to maintain hover state. Capture pointer events during drag. Users never notice these, and that is exactly right.\n\n5. **Use transitions, not keyframes, for dynamic UI.** Toasts are added rapidly. Keyframes restart from zero on interruption. Transitions retarget smoothly.\n\n6. **Build a great documentation site.** Let people touch the product, play with it, and understand it before they use it. Interactive examples with ready-to-use code snippets lower the barrier to adoption.\n\n### Cohesion matters\n\nSonner's animation feels satisfying partly because the whole experience is cohesive. The easing and duration fit the vibe of the library. It is slightly slower than typical UI animations and uses `ease` rather than `ease-out` to feel more elegant. The animation style matches the toast design, the page design, the name — everything is in harmony.\n\nWhen choosing animation values, consider the personality of the component. A playful component can be bouncier. A professional dashboard should be crisp and fast. Match the motion to the mood.\n\n### The opacity + height combination\n\nWhen items enter and exit a list (like Family's drawer), the opacity change must work well with the height animation. This is often trial and error. There is no formula — you adjust until it feels right.\n\n### Review your work the next day\n\nReview animations with fresh eyes. You notice imperfections the next day that you missed during development. Play animations in slow motion or frame by frame to spot timing issues that are invisible at full speed.\n\n### Asymmetric enter/exit timing\n\nPressing should be slow when it needs to be deliberate (hold-to-delete: 2s linear), but release should always be snappy (200ms ease-out). This pattern applies broadly: slow where the user is deciding, fast where the system is responding.\n\n```css\n/* Release: fast */\n.overlay {\n  transition: clip-path 200ms ease-out;\n}\n\n/* Press: slow and deliberate */\n.button:active .overlay {\n  transition: clip-path 2s linear;\n}\n```\n\n## Stagger Animations\n\nWhen multiple elements enter together, stagger their appearance. Each element animates in with a small delay after the previous one. This creates a cascading effect that feels more natural than everything appearing at once.\n\n```css\n.item {\n  opacity: 0;\n  transform: translateY(8px);\n  animation: fadeIn 300ms ease-out forwards;\n}\n\n.item:nth-child(1) {\n  animation-delay: 0ms;\n}\n.item:nth-child(2) {\n  animation-delay: 50ms;\n}\n.item:nth-child(3) {\n  animation-delay: 100ms;\n}\n.item:nth-child(4) {\n  animation-delay: 150ms;\n}\n\n@keyframes fadeIn {\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n```\n\nKeep stagger delays short (30-80ms between items). Long delays make the interface feel slow. Stagger is decorative — never block interaction while stagger animations are playing.\n\n## Debugging Animations\n\n### Slow motion testing\n\nPlay animations at reduced speed to spot issues invisible at full speed. Temporarily increase duration to 2-5x normal, or use browser DevTools animation inspector to slow playback.\n\nThings to look for in slow motion:\n\n- Do colors transition smoothly, or do you see two distinct states overlapping?\n- Does the easing feel right, or does it start/stop abruptly?\n- Is the transform-origin correct, or does the element scale from the wrong point?\n- Are multiple animated properties (opacity, transform, color) in sync?\n\n### Frame-by-frame inspection\n\nStep through animations frame by frame in Chrome DevTools (Animations panel). This reveals timing issues between coordinated properties that you cannot see at full speed.\n\n### Test on real devices\n\nFor touch interactions (drawers, swipe gestures), test on physical devices. Connect your phone via USB, visit your local dev server by IP address, and use Safari's remote devtools. The Xcode Simulator is an alternative but real hardware is better for gesture testing.\n\n## Review Checklist\n\nWhen reviewing UI code, check for:\n\n| Issue                                  | Fix                                                                                           |\n| -------------------------------------- | --------------------------------------------------------------------------------------------- |\n| `transition: all`                      | Specify exact properties: `transition: transform 200ms ease-out`                              |\n| `scale(0)` entry animation             | Start from `scale(0.95)` with `opacity: 0`                                                    |\n| `ease-in` on UI element                | Switch to `ease-out` or custom curve                                                          |\n| `transform-origin: center` on popover  | Set to trigger location or use Radix/Base UI CSS variable (modals are exempt — keep centered) |\n| Animation on keyboard action           | Remove animation entirely                                                                     |\n| Duration > 300ms on UI element         | Reduce to 150-250ms                                                                           |\n| Hover animation without media query    | Add `@media (hover: hover) and (pointer: fine)`                                               |\n| Keyframes on rapidly-triggered element | Use CSS transitions for interruptibility                                                      |\n| Framer Motion `x`/`y` props under load | Use `transform: \"translateX()\"` for hardware acceleration                                     |\n| Same enter/exit transition speed       | Make exit faster than enter (e.g., enter 2s, exit 200ms)                                      |\n| Elements all appear at once            | Add stagger delay (30-80ms between items)                                                     |\n\n<!-- File: .agents/skills/frontend-design/SKILL.md (Tokens: ~850 | Category: anthropic-skill) -->\n---\nname: frontend-design\ndescription: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.\n---\n\nThis skill guides creation of distinctive, production-grade frontend interfaces that avoid generic \"AI slop\" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.\n\nThe user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.\n\n## Design Thinking\n\nBefore coding, understand the context and commit to a BOLD aesthetic direction:\n- **Purpose**: What problem does this interface solve? Who uses it?\n- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.\n- **Constraints**: Technical requirements (framework, performance, accessibility).\n- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?\n\n**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.\n\nThen implement working code (HTML/CSS/JS, React, Vue, etc.) that is:\n- Production-grade and functional\n- Visually striking and memorable\n- Cohesive with a clear aesthetic point-of-view\n- Meticulously refined in every detail\n\n## Frontend Aesthetics Guidelines\n\nFocus on:\n- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.\n- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.\n- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.\n- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.\n- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.\n\nNEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.\n\nInterpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.\n\n**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.\n\nRemember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.\n\n<!-- File: .agents/skills/plan-mode/SKILL.md (Tokens: ~1429 | Category: anthropic-skill) -->\n---\nname: plan-mode\ndescription: Holistic, system-aware planning before implementing non-trivial tasks. Use when the task involves new features, architectural decisions, multi-file changes, unclear requirements, or multiple valid approaches. Triggers on \"/plan\", \"plan this\", \"design an approach\", \"let's plan first\".\n---\n\nFor non-trivial implementation tasks, plan before you code. Treat every change as a system change, not an isolated patch. Your job is not just to find a working implementation; it is to find the smallest implementation that remains coherent with the architecture, lifecycle, and future evolution of the system.\n\n## Planning Doctrine\n\n- **Think broadly, implement narrowly.** Analyze the surrounding system before deciding where the change belongs.\n- **Every change has system implications.** Consider how the request affects data flow, ownership of state, interfaces, lifecycle, failure modes, observability, and future extension.\n- **Prefer authoritative, derivable designs.** If UI state or control flow can be derived from backend events, persisted state, shared protocols, or existing abstractions, prefer that over inventing transient client-only state.\n- **Choose the smallest coherent solution.** Do not default to the smallest local patch if it distorts the architecture or adds hidden follow-on complexity.\n- **Expand scope only when it simplifies the system.** Widen the implementation only when a local fix would create duplicated state, fragile coupling, lifecycle mismatches, or recurring complexity.\n- **Do not gold-plate.** Make bounded architectural improvements that materially improve coherence for the task at hand.\n\n## Plan File\n\nWrite your plan to a `PLAN.md` file in the project root. Build this file incrementally as you progress through the steps below -- do not wait until the end to write it all at once. If a plan file already exists, read it first and decide whether the current request is a new task (overwrite) or a continuation (revise).\n\n## Step 1: Explore\n\nThoroughly explore the codebase to understand the request before designing anything.\n\n- Read the relevant files and understand existing patterns, architecture, and conventions.\n- Trace the end-to-end flow, not just the local implementation point.\n- Identify the current **source of truth**, state ownership, boundaries between subsystems, and any existing invariants.\n- Identify the full lifecycle of the behavior: trigger, processing, intermediate states, completion, side effects, failure, retry, and cleanup.\n- Search for similar features and prior art in the codebase.\n- **Launch parallel explorations** when the scope is uncertain or multiple areas of the codebase are involved. Give each exploration a specific, distinct search focus (e.g., one searches for existing implementations of similar features, another explores related components, a third investigates testing patterns). Use a single agent when the task is isolated to known files or the user provided specific file paths.\n- Do not start implementing yet.\n\n## Step 2: Clarify\n\nAsk the user questions to resolve ambiguities before committing to an approach. This may cover technical implementation, desired system behavior, UI/UX, performance, edge cases, ownership of state, failure handling, or tradeoffs. You may ask multiple rounds, reading more code in between. Do not make large assumptions about user intent.\n\n## Step 3: Design\n\nBased on exploration and user input, design a concrete implementation approach:\n\n- Provide comprehensive background context including filenames, code path traces, and the surrounding system behavior discovered in Step 1.\n- Treat the request as a system modification, not just a local diff.\n- Explicitly consider both:\n  - **The most direct implementation**\n  - **The most system-coherent implementation**\n- Choose the direct implementation only if it does **not** introduce architectural distortion, duplicated state, fragile coupling, lifecycle mismatches, or hidden follow-on complexity.\n- Prefer the smallest holistic solution: think broadly about implications, then keep the implementation as narrow as possible while preserving coherence.\n- For complex tasks, consider multiple perspectives to arrive at the best approach:\n  - **New feature**: simplicity vs performance vs maintainability\n  - **Bug fix**: root cause fix vs workaround vs prevention\n  - **Refactoring**: minimal change vs clean architecture\n- For tasks that touch multiple parts of the codebase, involve large refactors, or have many edge cases, explore different approaches in parallel before converging on a recommendation.\n\nBefore finalizing the design, explicitly answer these questions:\n\n1. What part of the system is actually changing?\n2. What is the source of truth before and after this change?\n3. What new state, transitions, or invariants does this introduce?\n4. What other components, flows, or interfaces now depend on this decision?\n5. Does this create duplicated logic or state anywhere?\n6. Is there a more system-coherent place to implement this?\n7. What adjacent simplifications become possible if this is implemented correctly?\n8. What is the smallest solution that keeps the system coherent?\n\n## Step 4: Review\n\nBefore finalizing, review your design against the original request:\n\n1. Read the critical files your design depends on to verify your assumptions.\n2. Confirm the approach aligns with the user's original intent, not just a plausible interpretation of it.\n3. Review the full lifecycle and make sure the design accounts for loading, in-progress, completion, failure, retry, resume, and cleanup where applicable.\n4. Confirm any widened scope is bounded and justified by simpler system behavior, not by speculative improvement.\n5. Ask remaining clarifying questions if anything is still ambiguous.\n\n## Step 5: Present the Plan\n\nWrite the final plan to the plan file. The plan should be:\n\n- **Concise enough to scan quickly, detailed enough to execute.** Include only your recommended approach, not all alternatives.\n- **Specific about files.** List the paths of critical files to be modified and what changes in each.\n- **Explicit about system impact.** Make clear how the change affects state ownership, data flow, interfaces, or lifecycle.\n- **Verifiable.** Include how to test the changes end-to-end.\n\nStructure:\n\n```\nSummary: 1-2 sentences on the task and chosen approach\n\nContext: Key findings from exploration -- existing patterns, relevant files, constraints\n\nSystem Impact: How the change affects source of truth, data flow, lifecycle, and dependent parts of the system\n\nApproach: High-level design decision and why\n\nChanges:\n- `path/to/file.ts` - what changes and why\n- `path/to/other.ts` - what changes and why\n\nVerification:\n- How to test end-to-end\n- Relevant test commands\n- Edge cases to check\n```\n\nPresent the plan to the user and wait for approval. If the user has feedback, revise accordingly.\n\n## Step 6: Implement and Verify\n\nOnce approved:\n\n1. Implement the plan, tracking progress against each item.\n2. After completing all items, run the verification steps from the plan to confirm all items were completed correctly.\n\n<!-- File: .agents/skills/remove-demo-limits/SKILL.md (Tokens: ~519 | Category: anthropic-skill) -->\n---\nname: remove-demo-limits\ndescription: Removes Open Harness hosted demo restrictions from a fork. Use when a maintainer wants to remove managed-template trial caps, hosted deployment gating, or \"deploy your own\" limits. Triggers on \"remove demo limits\", \"remove trial limits\", \"remove hosted restrictions\", \"open this up for my fork\", \"remove managed template restrictions\".\n---\n\nRemove the managed-template trial restrictions with the smallest coherent change.\n\n## Goal\nMake the fork behave like a normal self-hosted deployment:\n- no trial session cap\n- no trial message cap\n- no hosted-only restriction errors\n- no hosted-demo delete restrictions\n- no dead helper code or stale tests\n\n## Inspect first\nRead these files before editing:\n- `apps/web/lib/managed-template-trial.ts`\n- `apps/web/app/api/sessions/route.ts`\n- `apps/web/app/api/chat/route.ts`\n- `apps/web/app/api/sessions/[sessionId]/chats/[chatId]/messages/[messageId]/route.ts`\n- `apps/web/app/api/sessions/route.test.ts`\n- `apps/web/app/api/chat/route.test.ts`\n- `apps/web/app/api/sessions/[sessionId]/chats/[chatId]/messages/[messageId]/route.test.ts`\n\nThen search for these identifiers and strings to catch older or newer variants:\n- `isManagedTemplateTrialUser`\n- `MANAGED_TEMPLATE_TRIAL_`\n- `deploy-your-own`\n- `open-agents.dev`\n- `vercel.com`\n- `This hosted deployment includes`\n- `does not allow message deletion`\n\n## Required changes\n1. Remove the guards that block session creation, chat messages, or message deletion for managed-template users.\n2. If the auth callback still blocks non-Vercel emails on hosted deployments, remove that restriction too.\n3. Delete `apps/web/lib/managed-template-trial.ts` if it becomes unused; otherwise simplify it so no dead exports remain.\n4. Remove or update tests that assert the hosted caps, delete restrictions, or hosted-only auth gating.\n5. Do not add feature flags, env toggles, or new dependencies. Prefer deleting the restriction code outright.\n6. Keep unrelated auth, session ownership, and sandbox behavior unchanged.\n\n## Verification\nThis repo uses Bun. After the edits:\n- run `bun run ci`\n- if a failure is pre-existing, say that clearly and separate it from your changes\n\n## Output\nReport:\n- which restrictions were removed\n- which files changed\n- verification result\n\n<!-- File: .agents/skills/vercel-react-best-practices/AGENTS.md (Tokens: ~11965 | Category: anthropic-skill) -->\n# React Best Practices\n\n**Version 1.0.0**  \nVercel Engineering  \nJanuary 2026\n\n> **Note:**  \n> This document is mainly for agents and LLMs to follow when maintaining,  \n> generating, or refactoring React and Next.js codebases. Humans  \n> may also find it useful, but guidance here is optimized for automation  \n> and consistency by AI-assisted workflows.\n\n---\n\n## Abstract\n\nComprehensive 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.\n\n---\n\n## Table of Contents\n\n1. [Eliminating Waterfalls](#1-eliminating-waterfalls) — **CRITICAL**\n   - 1.1 [Defer Await Until Needed](#11-defer-await-until-needed)\n   - 1.2 [Dependency-Based Parallelization](#12-dependency-based-parallelization)\n   - 1.3 [Prevent Waterfall Chains in API Routes](#13-prevent-waterfall-chains-in-api-routes)\n   - 1.4 [Promise.all() for Independent Operations](#14-promiseall-for-independent-operations)\n   - 1.5 [Strategic Suspense Boundaries](#15-strategic-suspense-boundaries)\n2. [Bundle Size Optimization](#2-bundle-size-optimization) — **CRITICAL**\n   - 2.1 [Avoid Barrel File Imports](#21-avoid-barrel-file-imports)\n   - 2.2 [Conditional Module Loading](#22-conditional-module-loading)\n   - 2.3 [Defer Non-Critical Third-Party Libraries](#23-defer-non-critical-third-party-libraries)\n   - 2.4 [Dynamic Imports for Heavy Components](#24-dynamic-imports-for-heavy-components)\n   - 2.5 [Preload Based on User Intent](#25-preload-based-on-user-intent)\n3. [Server-Side Performance](#3-server-side-performance) — **HIGH**\n   - 3.1 [Authenticate Server Actions Like API Routes](#31-authenticate-server-actions-like-api-routes)\n   - 3.2 [Avoid Duplicate Serialization in RSC Props](#32-avoid-duplicate-serialization-in-rsc-props)\n   - 3.3 [Cross-Request LRU Caching](#33-cross-request-lru-caching)\n   - 3.4 [Hoist Static I/O to Module Level](#34-hoist-static-io-to-module-level)\n   - 3.5 [Minimize Serialization at RSC Boundaries](#35-minimize-serialization-at-rsc-boundaries)\n   - 3.6 [Parallel Data Fetching with Component Composition](#36-parallel-data-fetching-with-component-composition)\n   - 3.7 [Per-Request Deduplication with React.cache()](#37-per-request-deduplication-with-reactcache)\n   - 3.8 [Use after() for Non-Blocking Operations](#38-use-after-for-non-blocking-operations)\n4. [Client-Side Data Fetching](#4-client-side-data-fetching) — **MEDIUM-HIGH**\n   - 4.1 [Deduplicate Global Event Listeners](#41-deduplicate-global-event-listeners)\n   - 4.2 [Use Passive Event Listeners for Scrolling Performance](#42-use-passive-event-listeners-for-scrolling-performance)\n   - 4.3 [Use SWR for Automatic Deduplication](#43-use-swr-for-automatic-deduplication)\n   - 4.4 [Version and Minimize localStorage Data](#44-version-and-minimize-localstorage-data)\n5. [Re-render Optimization](#5-re-render-optimization) — **MEDIUM**\n   - 5.1 [Calculate Derived State During Rendering](#51-calculate-derived-state-during-rendering)\n   - 5.2 [Defer State Reads to Usage Point](#52-defer-state-reads-to-usage-point)\n   - 5.3 [Do not wrap a simple expression with a primitive result type in useMemo](#53-do-not-wrap-a-simple-expression-with-a-primitive-result-type-in-usememo)\n   - 5.4 [Extract Default Non-primitive Parameter Value from Memoized Component to Constant](#54-extract-default-non-primitive-parameter-value-from-memoized-component-to-constant)\n   - 5.5 [Extract to Memoized Components](#55-extract-to-memoized-components)\n   - 5.6 [Narrow Effect Dependencies](#56-narrow-effect-dependencies)\n   - 5.7 [Put Interaction Logic in Event Handlers](#57-put-interaction-logic-in-event-handlers)\n   - 5.8 [Subscribe to Derived State](#58-subscribe-to-derived-state)\n   - 5.9 [Use Functional setState Updates](#59-use-functional-setstate-updates)\n   - 5.10 [Use Lazy State Initialization](#510-use-lazy-state-initialization)\n   - 5.11 [Use Transitions for Non-Urgent Updates](#511-use-transitions-for-non-urgent-updates)\n   - 5.12 [Use useRef for Transient Values](#512-use-useref-for-transient-values)\n6. [Rendering Performance](#6-rendering-performance) — **MEDIUM**\n   - 6.1 [Animate SVG Wrapper Instead of SVG Element](#61-animate-svg-wrapper-instead-of-svg-element)\n   - 6.2 [CSS content-visibility for Long Lists](#62-css-content-visibility-for-long-lists)\n   - 6.3 [Hoist Static JSX Elements](#63-hoist-static-jsx-elements)\n   - 6.4 [Optimize SVG Precision](#64-optimize-svg-precision)\n   - 6.5 [Prevent Hydration Mismatch Without Flickering](#65-prevent-hydration-mismatch-without-flickering)\n   - 6.6 [Suppress Expected Hydration Mismatches](#66-suppress-expected-hydration-mismatches)\n   - 6.7 [Use Activity Component for Show/Hide](#67-use-activity-component-for-showhide)\n   - 6.8 [Use Explicit Conditional Rendering](#68-use-explicit-conditional-rendering)\n   - 6.9 [Use useTransition Over Manual Loading States](#69-use-usetransition-over-manual-loading-states)\n7. [JavaScript Performance](#7-javascript-performance) — **LOW-MEDIUM**\n   - 7.1 [Avoid Layout Thrashing](#71-avoid-layout-thrashing)\n   - 7.2 [Build Index Maps for Repeated Lookups](#72-build-index-maps-for-repeated-lookups)\n   - 7.3 [Cache Property Access in Loops](#73-cache-property-access-in-loops)\n   - 7.4 [Cache Repeated Function Calls](#74-cache-repeated-function-calls)\n   - 7.5 [Cache Storage API Calls](#75-cache-storage-api-calls)\n   - 7.6 [Combine Multiple Array Iterations](#76-combine-multiple-array-iterations)\n   - 7.7 [Early Length Check for Array Comparisons](#77-early-length-check-for-array-comparisons)\n   - 7.8 [Early Return from Functions](#78-early-return-from-functions)\n   - 7.9 [Hoist RegExp Creation](#79-hoist-regexp-creation)\n   - 7.10 [Use Loop for Min/Max Instead of Sort](#710-use-loop-for-minmax-instead-of-sort)\n   - 7.11 [Use Set/Map for O(1) Lookups](#711-use-setmap-for-o1-lookups)\n   - 7.12 [Use toSorted() Instead of sort() for Immutability](#712-use-tosorted-instead-of-sort-for-immutability)\n8. [Advanced Patterns](#8-advanced-patterns) — **LOW**\n   - 8.1 [Initialize App Once, Not Per Mount](#81-initialize-app-once-not-per-mount)\n   - 8.2 [Store Event Handlers in Refs](#82-store-event-handlers-in-refs)\n   - 8.3 [useEffectEvent for Stable Callback Refs](#83-useeffectevent-for-stable-callback-refs)\n\n---\n\n## 1. Eliminating Waterfalls\n\n**Impact: CRITICAL**\n\nWaterfalls are the #1 performance killer. Each sequential await adds full network latency. Eliminating them yields the largest gains.\n\n### 1.1 Defer Await Until Needed\n\n**Impact: HIGH (avoids blocking unused code paths)**\n\nMove `await` operations into the branches where they're actually used to avoid blocking code paths that don't need them.\n\n**Incorrect: blocks both branches**\n\n```typescript\nasync function handleRequest(userId: string, skipProcessing: boolean) {\n  const userData = await fetchUserData(userId)\n  \n  if (skipProcessing) {\n    // Returns immediately but still waited for userData\n    return { skipped: true }\n  }\n  \n  // Only this branch uses userData\n  return processUserData(userData)\n}\n```\n\n**Correct: only blocks when needed**\n\n```typescript\nasync function handleRequest(userId: string, skipProcessing: boolean) {\n  if (skipProcessing) {\n    // Returns immediately without waiting\n    return { skipped: true }\n  }\n  \n  // Fetch only when needed\n  const userData = await fetchUserData(userId)\n  return processUserData(userData)\n}\n```\n\n**Another example: early return optimization**\n\n```typescript\n// Incorrect: always fetches permissions\nasync function updateResource(resourceId: string, userId: string) {\n  const permissions = await fetchPermissions(userId)\n  const resource = await getResource(resourceId)\n  \n  if (!resource) {\n    return { error: 'Not found' }\n  }\n  \n  if (!permissions.canEdit) {\n    return { error: 'Forbidden' }\n  }\n  \n  return await updateResourceData(resource, permissions)\n}\n\n// Correct: fetches only when needed\nasync function updateResource(resourceId: string, userId: string) {\n  const resource = await getResource(resourceId)\n  \n  if (!resource) {\n    return { error: 'Not found' }\n  }\n  \n  const permissions = await fetchPermissions(userId)\n  \n  if (!permissions.canEdit) {\n    return { error: 'Forbidden' }\n  }\n  \n  return await updateResourceData(resource, permissions)\n}\n```\n\nThis optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive.\n\n### 1.2 Dependency-Based Parallelization\n\n**Impact: CRITICAL (2-10× improvement)**\n\nFor operations with partial dependencies, use `better-all` to maximize parallelism. It automatically starts each task at the earliest possible moment.\n\n**Incorrect: profile waits for config unnecessarily**\n\n```typescript\nconst [user, config] = await Promise.all([\n  fetchUser(),\n  fetchConfig()\n])\nconst profile = await fetchProfile(user.id)\n```\n\n**Correct: config and profile run in parallel**\n\n```typescript\nimport { all } from 'better-all'\n\nconst { user, config, profile } = await all({\n  async user() { return fetchUser() },\n  async config() { return fetchConfig() },\n  async profile() {\n    return fetchProfile((await this.$.user).id)\n  }\n})\n```\n\n**Alternative without extra dependencies:**\n\n```typescript\nconst userPromise = fetchUser()\nconst profilePromise = userPromise.then(user => fetchProfile(user.id))\n\nconst [user, config, profile] = await Promise.all([\n  userPromise,\n  fetchConfig(),\n  profilePromise\n])\n```\n\nWe can also create all the promises first, and do `Promise.all()` at the end.\n\nReference: [https://github.com/shuding/better-all](https://github.com/shuding/better-all)\n\n### 1.3 Prevent Waterfall Chains in API Routes\n\n**Impact: CRITICAL (2-10× improvement)**\n\nIn API routes and Server Actions, start independent operations immediately, even if you don't await them yet.\n\n**Incorrect: config waits for auth, data waits for both**\n\n```typescript\nexport async function GET(request: Request) {\n  const session = await auth()\n  const config = await fetchConfig()\n  const data = await fetchData(session.user.id)\n  return Response.json({ data, config })\n}\n```\n\n**Correct: auth and config start immediately**\n\n```typescript\nexport async function GET(request: Request) {\n  const sessionPromise = auth()\n  const configPromise = fetchConfig()\n  const session = await sessionPromise\n  const [config, data] = await Promise.all([\n    configPromise,\n    fetchData(session.user.id)\n  ])\n  return Response.json({ data, config })\n}\n```\n\nFor operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization).\n\n### 1.4 Promise.all() for Independent Operations\n\n**Impact: CRITICAL (2-10× improvement)**\n\nWhen async operations have no interdependencies, execute them concurrently using `Promise.all()`.\n\n**Incorrect: sequential execution, 3 round trips**\n\n```typescript\nconst user = await fetchUser()\nconst posts = await fetchPosts()\nconst comments = await fetchComments()\n```\n\n**Correct: parallel execution, 1 round trip**\n\n```typescript\nconst [user, posts, comments] = await Promise.all([\n  fetchUser(),\n  fetchPosts(),\n  fetchComments()\n])\n```\n\n### 1.5 Strategic Suspense Boundaries\n\n**Impact: HIGH (faster initial paint)**\n\nInstead of awaiting data in async components before returning JSX, use Suspense boundaries to show the wrapper UI faster while data loads.\n\n**Incorrect: wrapper blocked by data fetching**\n\n```tsx\nasync function Page() {\n  const data = await fetchData() // Blocks entire page\n  \n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <div>\n        <DataDisplay data={data} />\n      </div>\n      <div>Footer</div>\n    </div>\n  )\n}\n```\n\nThe entire layout waits for data even though only the middle section needs it.\n\n**Correct: wrapper shows immediately, data streams in**\n\n```tsx\nfunction Page() {\n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <div>\n        <Suspense fallback={<Skeleton />}>\n          <DataDisplay />\n        </Suspense>\n      </div>\n      <div>Footer</div>\n    </div>\n  )\n}\n\nasync function DataDisplay() {\n  const data = await fetchData() // Only blocks this component\n  return <div>{data.content}</div>\n}\n```\n\nSidebar, Header, and Footer render immediately. Only DataDisplay waits for data.\n\n**Alternative: share promise across components**\n\n```tsx\nfunction Page() {\n  // Start fetch immediately, but don't await\n  const dataPromise = fetchData()\n  \n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <Suspense fallback={<Skeleton />}>\n        <DataDisplay dataPromise={dataPromise} />\n        <DataSummary dataPromise={dataPromise} />\n      </Suspense>\n      <div>Footer</div>\n    </div>\n  )\n}\n\nfunction DataDisplay({ dataPromise }: { dataPromise: Promise<Data> }) {\n  const data = use(dataPromise) // Unwraps the promise\n  return <div>{data.content}</div>\n}\n\nfunction DataSummary({ dataPromise }: { dataPromise: Promise<Data> }) {\n  const data = use(dataPromise) // Reuses the same promise\n  return <div>{data.summary}</div>\n}\n```\n\nBoth components share the same promise, so only one fetch occurs. Layout renders immediately while both components wait together.\n\n**When NOT to use this pattern:**\n\n- Critical data needed for layout decisions (affects positioning)\n\n- SEO-critical content above the fold\n\n- Small, fast queries where suspense overhead isn't worth it\n\n- When you want to avoid layout shift (loading → content jump)\n\n**Trade-off:** Faster initial paint vs potential layout shift. Choose based on your UX priorities.\n\n---\n\n## 2. Bundle Size Optimization\n\n**Impact: CRITICAL**\n\nReducing initial bundle size improves Time to Interactive and Largest Contentful Paint.\n\n### 2.1 Avoid Barrel File Imports\n\n**Impact: CRITICAL (200-800ms import cost, slow builds)**\n\nImport directly from source files instead of barrel files to avoid loading thousands of unused modules. **Barrel files** are entry points that re-export multiple modules (e.g., `index.js` that does `export * from './module'`).\n\nPopular icon and component libraries can have **up to 10,000 re-exports** in their entry file. For many React packages, **it takes 200-800ms just to import them**, affecting both development speed and production cold starts.\n\n**Why tree-shaking doesn't help:** When a library is marked as external (not bundled), the bundler can't optimize it. If you bundle it to enable tree-shaking, builds become substantially slower analyzing the entire module graph.\n\n**Incorrect: imports entire library**\n\n```tsx\nimport { Check, X, Menu } from 'lucide-react'\n// Loads 1,583 modules, takes ~2.8s extra in dev\n// Runtime cost: 200-800ms on every cold start\n\nimport { Button, TextField } from '@mui/material'\n// Loads 2,225 modules, takes ~4.2s extra in dev\n```\n\n**Correct: imports only what you need**\n\n```tsx\nimport Check from 'lucide-react/dist/esm/icons/check'\nimport X from 'lucide-react/dist/esm/icons/x'\nimport Menu from 'lucide-react/dist/esm/icons/menu'\n// Loads only 3 modules (~2KB vs ~1MB)\n\nimport Button from '@mui/material/Button'\nimport TextField from '@mui/material/TextField'\n// Loads only what you use\n```\n\n**Alternative: Next.js 13.5+**\n\n```js\n// next.config.js - use optimizePackageImports\nmodule.exports = {\n  experimental: {\n    optimizePackageImports: ['lucide-react', '@mui/material']\n  }\n}\n\n// Then you can keep the ergonomic barrel imports:\nimport { Check, X, Menu } from 'lucide-react'\n// Automatically transformed to direct imports at build time\n```\n\nDirect imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR.\n\nLibraries commonly affected: `lucide-react`, `@mui/material`, `@mui/icons-material`, `@tabler/icons-react`, `react-icons`, `@headlessui/react`, `@radix-ui/react-*`, `lodash`, `ramda`, `date-fns`, `rxjs`, `react-use`.\n\nReference: [https://vercel.com/blog/how-we-optimized-package-imports-in-next-js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)\n\n### 2.2 Conditional Module Loading\n\n**Impact: HIGH (loads large data only when needed)**\n\nLoad large data or modules only when a feature is activated.\n\n**Example: lazy-load animation frames**\n\n```tsx\nfunction AnimationPlayer({ enabled, setEnabled }: { enabled: boolean; setEnabled: React.Dispatch<React.SetStateAction<boolean>> }) {\n  const [frames, setFrames] = useState<Frame[] | null>(null)\n\n  useEffect(() => {\n    if (enabled && !frames && typeof window !== 'undefined') {\n      import('./animation-frames.js')\n        .then(mod => setFrames(mod.frames))\n        .catch(() => setEnabled(false))\n    }\n  }, [enabled, frames, setEnabled])\n\n  if (!frames) return <Skeleton />\n  return <Canvas frames={frames} />\n}\n```\n\nThe `typeof window !== 'undefined'` check prevents bundling this module for SSR, optimizing server bundle size and build speed.\n\n### 2.3 Defer Non-Critical Third-Party Libraries\n\n**Impact: MEDIUM (loads after hydration)**\n\nAnalytics, logging, and error tracking don't block user interaction. Load them after hydration.\n\n**Incorrect: blocks initial bundle**\n\n```tsx\nimport { Analytics } from '@vercel/analytics/react'\n\nexport default function RootLayout({ children }) {\n  return (\n    <html>\n      <body>\n        {children}\n        <Analytics />\n      </body>\n    </html>\n  )\n}\n```\n\n**Correct: loads after hydration**\n\n```tsx\nimport dynamic from 'next/dynamic'\n\nconst Analytics = dynamic(\n  () => import('@vercel/analytics/react').then(m => m.Analytics),\n  { ssr: false }\n)\n\nexport default function RootLayout({ children }) {\n  return (\n    <html>\n      <body>\n        {children}\n        <Analytics />\n      </body>\n    </html>\n  )\n}\n```\n\n### 2.4 Dynamic Imports for Heavy Components\n\n**Impact: CRITICAL (directly affects TTI and LCP)**\n\nUse `next/dynamic` to lazy-load large components not needed on initial render.\n\n**Incorrect: Monaco bundles with main chunk ~300KB**\n\n```tsx\nimport { MonacoEditor } from './monaco-editor'\n\nfunction CodePanel({ code }: { code: string }) {\n  return <MonacoEditor value={code} />\n}\n```\n\n**Correct: Monaco loads on demand**\n\n```tsx\nimport dynamic from 'next/dynamic'\n\nconst MonacoEditor = dynamic(\n  () => import('./monaco-editor').then(m => m.MonacoEditor),\n  { ssr: false }\n)\n\nfunction CodePanel({ code }: { code: string }) {\n  return <MonacoEditor value={code} />\n}\n```\n\n### 2.5 Preload Based on User Intent\n\n**Impact: MEDIUM (reduces perceived latency)**\n\nPreload heavy bundles before they're needed to reduce perceived latency.\n\n**Example: preload on hover/focus**\n\n```tsx\nfunction EditorButton({ onClick }: { onClick: () => void }) {\n  const preload = () => {\n    if (typeof window !== 'undefined') {\n      void import('./monaco-editor')\n    }\n  }\n\n  return (\n    <button\n      onMouseEnter={preload}\n      onFocus={preload}\n      onClick={onClick}\n    >\n      Open Editor\n    </button>\n  )\n}\n```\n\n**Example: preload when feature flag is enabled**\n\n```tsx\nfunction FlagsProvider({ children, flags }: Props) {\n  useEffect(() => {\n    if (flags.editorEnabled && typeof window !== 'undefined') {\n      void import('./monaco-editor').then(mod => mod.init())\n    }\n  }, [flags.editorEnabled])\n\n  return <FlagsContext.Provider value={flags}>\n    {children}\n  </FlagsContext.Provider>\n}\n```\n\nThe `typeof window !== 'undefined'` check prevents bundling preloaded modules for SSR, optimizing server bundle size and build speed.\n\n---\n\n## 3. Server-Side Performance\n\n**Impact: HIGH**\n\nOptimizing server-side rendering and data fetching eliminates server-side waterfalls and reduces response times.\n\n### 3.1 Authenticate Server Actions Like API Routes\n\n**Impact: CRITICAL (prevents unauthorized access to server mutations)**\n\nServer Actions (functions with `\"use server\"`) are exposed as public endpoints, just like API routes. Always verify authentication and authorization **inside** each Server Action—do not rely solely on middleware, layout guards, or page-level checks, as Server Actions can be invoked directly.\n\nNext.js documentation explicitly states: \"Treat Server Actions with the same security considerations as public-facing API endpoints, and verify if the user is allowed to perform a mutation.\"\n\n**Incorrect: no authentication check**\n\n```typescript\n'use server'\n\nexport async function deleteUser(userId: string) {\n  // Anyone can call this! No auth check\n  await db.user.delete({ where: { id: userId } })\n  return { success: true }\n}\n```\n\n**Correct: authentication inside the action**\n\n```typescript\n'use server'\n\nimport { verifySession } from '@/lib/auth'\nimport { unauthorized } from '@/lib/errors'\n\nexport async function deleteUser(userId: string) {\n  // Always check auth inside the action\n  const session = await verifySession()\n  \n  if (!session) {\n    throw unauthorized('Must be logged in')\n  }\n  \n  // Check authorization too\n  if (session.user.role !== 'admin' && session.user.id !== userId) {\n    throw unauthorized('Cannot delete other users')\n  }\n  \n  await db.user.delete({ where: { id: userId } })\n  return { success: true }\n}\n```\n\n**With input validation:**\n\n```typescript\n'use server'\n\nimport { verifySession } from '@/lib/auth'\nimport { z } from 'zod'\n\nconst updateProfileSchema = z.object({\n  userId: z.string().uuid(),\n  name: z.string().min(1).max(100),\n  email: z.string().email()\n})\n\nexport async function updateProfile(data: unknown) {\n  // Validate input first\n  const validated = updateProfileSchema.parse(data)\n  \n  // Then authenticate\n  const session = await verifySession()\n  if (!session) {\n    throw new Error('Unauthorized')\n  }\n  \n  // Then authorize\n  if (session.user.id !== validated.userId) {\n    throw new Error('Can only update own profile')\n  }\n  \n  // Finally perform the mutation\n  await db.user.update({\n    where: { id: validated.userId },\n    data: {\n      name: validated.name,\n      email: validated.email\n    }\n  })\n  \n  return { success: true }\n}\n```\n\nReference: [https://nextjs.org/docs/app/guides/authentication](https://nextjs.org/docs/app/guides/authentication)\n\n### 3.2 Avoid Duplicate Serialization in RSC Props\n\n**Impact: LOW (reduces network payload by avoiding duplicate serialization)**\n\nRSC→client serialization deduplicates by object reference, not value. Same reference = serialized once; new reference = serialized again. Do transformations (`.toSorted()`, `.filter()`, `.map()`) in client, not server.\n\n**Incorrect: duplicates array**\n\n```tsx\n// RSC: sends 6 strings (2 arrays × 3 items)\n<ClientList usernames={usernames} usernamesOrdered={usernames.toSorted()} />\n```\n\n**Correct: sends 3 strings**\n\n```tsx\n// RSC: send once\n<ClientList usernames={usernames} />\n\n// Client: transform there\n'use client'\nconst sorted = useMemo(() => [...usernames].sort(), [usernames])\n```\n\n**Nested deduplication behavior:**\n\n```tsx\n// string[] - duplicates everything\nusernames={['a','b']} sorted={usernames.toSorted()} // sends 4 strings\n\n// object[] - duplicates array structure only\nusers={[{id:1},{id:2}]} sorted={users.toSorted()} // sends 2 arrays + 2 unique objects (not 4)\n```\n\nDeduplication works recursively. Impact varies by data type:\n\n- `string[]`, `number[]`, `boolean[]`: **HIGH impact** - array + all primitives fully duplicated\n\n- `object[]`: **LOW impact** - array duplicated, but nested objects deduplicated by reference\n\n**Operations breaking deduplication: create new references**\n\n- Arrays: `.toSorted()`, `.filter()`, `.map()`, `.slice()`, `[...arr]`\n\n- Objects: `{...obj}`, `Object.assign()`, `structuredClone()`, `JSON.parse(JSON.stringify())`\n\n**More examples:**\n\n```tsx\n// ❌ Bad\n<C users={users} active={users.filter(u => u.active)} />\n<C product={product} productName={product.name} />\n\n// ✅ Good\n<C users={users} />\n<C product={product} />\n// Do filtering/destructuring in client\n```\n\n**Exception:** Pass derived data when transformation is expensive or client doesn't need original.\n\n### 3.3 Cross-Request LRU Caching\n\n**Impact: HIGH (caches across requests)**\n\n`React.cache()` only works within one request. For data shared across sequential requests (user clicks button A then button B), use an LRU cache.\n\n**Implementation:**\n\n```typescript\nimport { LRUCache } from 'lru-cache'\n\nconst cache = new LRUCache<string, any>({\n  max: 1000,\n  ttl: 5 * 60 * 1000  // 5 minutes\n})\n\nexport async function getUser(id: string) {\n  const cached = cache.get(id)\n  if (cached) return cached\n\n  const user = await db.user.findUnique({ where: { id } })\n  cache.set(id, user)\n  return user\n}\n\n// Request 1: DB query, result cached\n// Request 2: cache hit, no DB query\n```\n\nUse when sequential user actions hit multiple endpoints needing the same data within seconds.\n\n**With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute):** LRU caching is especially effective because multiple concurrent requests can share the same function instance and cache. This means the cache persists across requests without needing external storage like Redis.\n\n**In traditional serverless:** Each invocation runs in isolation, so consider Redis for cross-process caching.\n\nReference: [https://github.com/isaacs/node-lru-cache](https://github.com/isaacs/node-lru-cache)\n\n### 3.4 Hoist Static I/O to Module Level\n\n**Impact: HIGH (avoids repeated file/network I/O per request)**\n\nWhen loading static assets (fonts, logos, images, config files) in route handlers or server functions, hoist the I/O operation to module level. Module-level code runs once when the module is first imported, not on every request. This eliminates redundant file system reads or network fetches that would otherwise run on every invocation.\n\n**Incorrect: reads font file on every request**\n\n**Correct: loads once at module initialization**\n\n**Alternative: synchronous file reads with Node.js fs**\n\n**General Node.js example: loading config or templates**\n\n**When to use this pattern:**\n\n- Loading fonts for OG image generation\n\n- Loading static logos, icons, or watermarks\n\n- Reading configuration files that don't change at runtime\n\n- Loading email templates or other static templates\n\n- Any static asset that's the same across all requests\n\n**When NOT to use this pattern:**\n\n- Assets that vary per request or user\n\n- Files that may change during runtime (use caching with TTL instead)\n\n- Large files that would consume too much memory if kept loaded\n\n- Sensitive data that shouldn't persist in memory\n\n**With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute):** Module-level caching is especially effective because multiple concurrent requests share the same function instance. The static assets stay loaded in memory across requests without cold start penalties.\n\n**In traditional serverless:** Each cold start re-executes module-level code, but subsequent warm invocations reuse the loaded assets until the instance is recycled.\n\n### 3.5 Minimize Serialization at RSC Boundaries\n\n**Impact: HIGH (reduces data transfer size)**\n\nThe React Server/Client boundary serializes all object properties into strings and embeds them in the HTML response and subsequent RSC requests. This serialized data directly impacts page weight and load time, so **size matters a lot**. Only pass fields that the client actually uses.\n\n**Incorrect: serializes all 50 fields**\n\n```tsx\nasync function Page() {\n  const user = await fetchUser()  // 50 fields\n  return <Profile user={user} />\n}\n\n'use client'\nfunction Profile({ user }: { user: User }) {\n  return <div>{user.name}</div>  // uses 1 field\n}\n```\n\n**Correct: serializes only 1 field**\n\n```tsx\nasync function Page() {\n  const user = await fetchUser()\n  return <Profile name={user.name} />\n}\n\n'use client'\nfunction Profile({ name }: { name: string }) {\n  return <div>{name}</div>\n}\n```\n\n### 3.6 Parallel Data Fetching with Component Composition\n\n**Impact: CRITICAL (eliminates server-side waterfalls)**\n\nReact Server Components execute sequentially within a tree. Restructure with composition to parallelize data fetching.\n\n**Incorrect: Sidebar waits for Page's fetch to complete**\n\n```tsx\nexport default async function Page() {\n  const header = await fetchHeader()\n  return (\n    <div>\n      <div>{header}</div>\n      <Sidebar />\n    </div>\n  )\n}\n\nasync function Sidebar() {\n  const items = await fetchSidebarItems()\n  return <nav>{items.map(renderItem)}</nav>\n}\n```\n\n**Correct: both fetch simultaneously**\n\n```tsx\nasync function Header() {\n  const data = await fetchHeader()\n  return <div>{data}</div>\n}\n\nasync function Sidebar() {\n  const items = await fetchSidebarItems()\n  return <nav>{items.map(renderItem)}</nav>\n}\n\nexport default function Page() {\n  return (\n    <div>\n      <Header />\n      <Sidebar />\n    </div>\n  )\n}\n```\n\n**Alternative with children prop:**\n\n```tsx\nasync function Header() {\n  const data = await fetchHeader()\n  return <div>{data}</div>\n}\n\nasync function Sidebar() {\n  const items = await fetchSidebarItems()\n  return <nav>{items.map(renderItem)}</nav>\n}\n\nfunction Layout({ children }: { children: ReactNode }) {\n  return (\n    <div>\n      <Header />\n      {children}\n    </div>\n  )\n}\n\nexport default function Page() {\n  return (\n    <Layout>\n      <Sidebar />\n    </Layout>\n  )\n}\n```\n\n### 3.7 Per-Request Deduplication with React.cache()\n\n**Impact: MEDIUM (deduplicates within request)**\n\nUse `React.cache()` for server-side request deduplication. Authentication and database queries benefit most.\n\n**Usage:**\n\n```typescript\nimport { cache } from 'react'\n\nexport const getCurrentUser = cache(async () => {\n  const session = await auth()\n  if (!session?.user?.id) return null\n  return await db.user.findUnique({\n    where: { id: session.user.id }\n  })\n})\n```\n\nWithin a single request, multiple calls to `getCurrentUser()` execute the query only once.\n\n**Avoid inline objects as arguments:**\n\n`React.cache()` uses shallow equality (`Object.is`) to determine cache hits. Inline objects create new references each call, preventing cache hits.\n\n**Incorrect: always cache miss**\n\n```typescript\nconst getUser = cache(async (params: { uid: number }) => {\n  return await db.user.findUnique({ where: { id: params.uid } })\n})\n\n// Each call creates new object, never hits cache\ngetUser({ uid: 1 })\ngetUser({ uid: 1 })  // Cache miss, runs query again\n```\n\n**Correct: cache hit**\n\n```typescript\nconst params = { uid: 1 }\ngetUser(params)  // Query runs\ngetUser(params)  // Cache hit (same reference)\n```\n\nIf you must pass objects, pass the same reference:\n\n**Next.js-Specific Note:**\n\nIn Next.js, the `fetch` API is automatically extended with request memoization. Requests with the same URL and options are automatically deduplicated within a single request, so you don't need `React.cache()` for `fetch` calls. However, `React.cache()` is still essential for other async tasks:\n\n- Database queries (Prisma, Drizzle, etc.)\n\n- Heavy computations\n\n- Authentication checks\n\n- File system operations\n\n- Any non-fetch async work\n\nUse `React.cache()` to deduplicate these operations across your component tree.\n\nReference: [https://react.dev/reference/react/cache](https://react.dev/reference/react/cache)\n\n### 3.8 Use after() for Non-Blocking Operations\n\n**Impact: MEDIUM (faster response times)**\n\nUse Next.js's `after()` to schedule work that should execute after a response is sent. This prevents logging, analytics, and other side effects from blocking the response.\n\n**Incorrect: blocks response**\n\n```tsx\nimport { logUserAction } from '@/app/utils'\n\nexport async function POST(request: Request) {\n  // Perform mutation\n  await updateDatabase(request)\n  \n  // Logging blocks the response\n  const userAgent = request.headers.get('user-agent') || 'unknown'\n  await logUserAction({ userAgent })\n  \n  return new Response(JSON.stringify({ status: 'success' }), {\n    status: 200,\n    headers: { 'Content-Type': 'application/json' }\n  })\n}\n```\n\n**Correct: non-blocking**\n\n```tsx\nimport { after } from 'next/server'\nimport { headers, cookies } from 'next/headers'\nimport { logUserAction } from '@/app/utils'\n\nexport async function POST(request: Request) {\n  // Perform mutation\n  await updateDatabase(request)\n  \n  // Log after response is sent\n  after(async () => {\n    const userAgent = (await headers()).get('user-agent') || 'unknown'\n    const sessionCookie = (await cookies()).get('session-id')?.value || 'anonymous'\n    \n    logUserAction({ sessionCookie, userAgent })\n  })\n  \n  return new Response(JSON.stringify({ status: 'success' }), {\n    status: 200,\n    headers: { 'Content-Type': 'application/json' }\n  })\n}\n```\n\nThe response is sent immediately while logging happens in the background.\n\n**Common use cases:**\n\n- Analytics tracking\n\n- Audit logging\n\n- Sending notifications\n\n- Cache invalidation\n\n- Cleanup tasks\n\n**Important notes:**\n\n- `after()` runs even if the response fails or redirects\n\n- Works in Server Actions, Route Handlers, and Server Components\n\nReference: [https://nextjs.org/docs/app/api-reference/functions/after](https://nextjs.org/docs/app/api-reference/functions/after)\n\n---\n\n## 4. Client-Side Data Fetching\n\n**Impact: MEDIUM-HIGH**\n\nAutomatic deduplication and efficient data fetching patterns reduce redundant network requests.\n\n### 4.1 Deduplicate Global Event Listeners\n\n**Impact: LOW (single listener for N components)**\n\nUse `useSWRSubscription()` to share global event listeners across component instances.\n\n**Incorrect: N instances = N listeners**\n\n```tsx\nfunction useKeyboardShortcut(key: string, callback: () => void) {\n  useEffect(() => {\n    const handler = (e: KeyboardEvent) => {\n      if (e.metaKey && e.key === key) {\n        callback()\n      }\n    }\n    window.addEventListener('keydown', handler)\n    return () => window.removeEventListener('keydown', handler)\n  }, [key, callback])\n}\n```\n\nWhen using the `useKeyboardShortcut` hook multiple times, each instance will register a new listener.\n\n**Correct: N instances = 1 listener**\n\n```tsx\nimport useSWRSubscription from 'swr/subscription'\n\n// Module-level Map to track callbacks per key\nconst keyCallbacks = new Map<string, Set<() => void>>()\n\nfunction useKeyboardShortcut(key: string, callback: () => void) {\n  // Register this callback in the Map\n  useEffect(() => {\n    if (!keyCallbacks.has(key)) {\n      keyCallbacks.set(key, new Set())\n    }\n    keyCallbacks.get(key)!.add(callback)\n\n    return () => {\n      const set = keyCallbacks.get(key)\n      if (set) {\n        set.delete(callback)\n        if (set.size === 0) {\n          keyCallbacks.delete(key)\n        }\n      }\n    }\n  }, [key, callback])\n\n  useSWRSubscription('global-keydown', () => {\n    const handler = (e: KeyboardEvent) => {\n      if (e.metaKey && keyCallbacks.has(e.key)) {\n        keyCallbacks.get(e.key)!.forEach(cb => cb())\n      }\n    }\n    window.addEventListener('keydown', handler)\n    return () => window.removeEventListener('keydown', handler)\n  })\n}\n\nfunction Profile() {\n  // Multiple shortcuts will share the same listener\n  useKeyboardShortcut('p', () => { /* ... */ }) \n  useKeyboardShortcut('k', () => { /* ... */ })\n  // ...\n}\n```\n\n### 4.2 Use Passive Event Listeners for Scrolling Performance\n\n**Impact: MEDIUM (eliminates scroll delay caused by event listeners)**\n\nAdd `{ passive: true }` to touch and wheel event listeners to enable immediate scrolling. Browsers normally wait for listeners to finish to check if `preventDefault()` is called, causing scroll delay.\n\n**Incorrect:**\n\n```typescript\nuseEffect(() => {\n  const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)\n  const handleWheel = (e: WheelEvent) => console.log(e.deltaY)\n  \n  document.addEventListener('touchstart', handleTouch)\n  document.addEventListener('wheel', handleWheel)\n  \n  return () => {\n    document.removeEventListener('touchstart', handleTouch)\n    document.removeEventListener('wheel', handleWheel)\n  }\n}, [])\n```\n\n**Correct:**\n\n```typescript\nuseEffect(() => {\n  const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)\n  const handleWheel = (e: WheelEvent) => console.log(e.deltaY)\n  \n  document.addEventListener('touchstart', handleTouch, { passive: true })\n  document.addEventListener('wheel', handleWheel, { passive: true })\n  \n  return () => {\n    document.removeEventListener('touchstart', handleTouch)\n    document.removeEventListener('wheel', handleWheel)\n  }\n}, [])\n```\n\n**Use passive when:** tracking/analytics, logging, any listener that doesn't call `preventDefault()`.\n\n**Don't use passive when:** implementing custom swipe gestures, custom zoom controls, or any listener that needs `preventDefault()`.\n\n### 4.3 Use SWR for Automatic Deduplication\n\n**Impact: MEDIUM-HIGH (automatic deduplication)**\n\nSWR enables request deduplication, caching, and revalidation across component instances.\n\n**Incorrect: no deduplication, each instance fetches**\n\n```tsx\nfunction UserList() {\n  const [users, setUsers] = useState([])\n  useEffect(() => {\n    fetch('/api/users')\n      .then(r => r.json())\n      .then(setUsers)\n  }, [])\n}\n```\n\n**Correct: multiple instances share one request**\n\n```tsx\nimport useSWR from 'swr'\n\nfunction UserList() {\n  const { data: users } = useSWR('/api/users', fetcher)\n}\n```\n\n**For immutable data:**\n\n```tsx\nimport { useImmutableSWR } from '@/lib/swr'\n\nfunction StaticContent() {\n  const { data } = useImmutableSWR('/api/config', fetcher)\n}\n```\n\n**For mutations:**\n\n```tsx\nimport { useSWRMutation } from 'swr/mutation'\n\nfunction UpdateButton() {\n  const { trigger } = useSWRMutation('/api/user', updateUser)\n  return <button onClick={() => trigger()}>Update</button>\n}\n```\n\nReference: [https://swr.vercel.app](https://swr.vercel.app)\n\n### 4.4 Version and Minimize localStorage Data\n\n**Impact: MEDIUM (prevents schema conflicts, reduces storage size)**\n\nAdd version prefix to keys and store only needed fields. Prevents schema conflicts and accidental storage of sensitive data.\n\n**Incorrect:**\n\n```typescript\n// No version, stores everything, no error handling\nlocalStorage.setItem('userConfig', JSON.stringify(fullUserObject))\nconst data = localStorage.getItem('userConfig')\n```\n\n**Correct:**\n\n```typescript\nconst VERSION = 'v2'\n\nfunction saveConfig(config: { theme: string; language: string }) {\n  try {\n    localStorage.setItem(`userConfig:${VERSION}`, JSON.stringify(config))\n  } catch {\n    // Throws in incognito/private browsing, quota exceeded, or disabled\n  }\n}\n\nfunction loadConfig() {\n  try {\n    const data = localStorage.getItem(`userConfig:${VERSION}`)\n    return data ? JSON.parse(data) : null\n  } catch {\n    return null\n  }\n}\n\n// Migration from v1 to v2\nfunction migrate() {\n  try {\n    const v1 = localStorage.getItem('userConfig:v1')\n    if (v1) {\n      const old = JSON.parse(v1)\n      saveConfig({ theme: old.darkMode ? 'dark' : 'light', language: old.lang })\n      localStorage.removeItem('userConfig:v1')\n    }\n  } catch {}\n}\n```\n\n**Store minimal fields from server responses:**\n\n```typescript\n// User object has 20+ fields, only store what UI needs\nfunction cachePrefs(user: FullUser) {\n  try {\n    localStorage.setItem('prefs:v1', JSON.stringify({\n      theme: user.preferences.theme,\n      notifications: user.preferences.notifications\n    }))\n  } catch {}\n}\n```\n\n**Always wrap in try-catch:** `getItem()` and `setItem()` throw in incognito/private browsing (Safari, Firefox), when quota exceeded, or when disabled.\n\n**Benefits:** Schema evolution via versioning, reduced storage size, prevents storing tokens/PII/internal flags.\n\n---\n\n## 5. Re-render Optimization\n\n**Impact: MEDIUM**\n\nReducing unnecessary re-renders minimizes wasted computation and improves UI responsiveness.\n\n### 5.1 Calculate Derived State During Rendering\n\n**Impact: MEDIUM (avoids redundant renders and state drift)**\n\nIf a value can be computed from current props/state, do not store it in state or update it in an effect. Derive it during render to avoid extra renders and state drift. Do not set state in effects solely in response to prop changes; prefer derived values or keyed resets instead.\n\n**Incorrect: redundant state and effect**\n\n```tsx\nfunction Form() {\n  const [firstName, setFirstName] = useState('First')\n  const [lastName, setLastName] = useState('Last')\n  const [fullName, setFullName] = useState('')\n\n  useEffect(() => {\n    setFullName(firstName + ' ' + lastName)\n  }, [firstName, lastName])\n\n  return <p>{fullName}</p>\n}\n```\n\n**Correct: derive during render**\n\n```tsx\nfunction Form() {\n  const [firstName, setFirstName] = useState('First')\n  const [lastName, setLastName] = useState('Last')\n  const fullName = firstName + ' ' + lastName\n\n  return <p>{fullName}</p>\n}\n```\n\nReference: [https://react.dev/learn/you-might-not-need-an-effect](https://react.dev/learn/you-might-not-need-an-effect)\n\n### 5.2 Defer State Reads to Usage Point\n\n**Impact: MEDIUM (avoids unnecessary subscriptions)**\n\nDon't subscribe to dynamic state (searchParams, localStorage) if you only read it inside callbacks.\n\n**Incorrect: subscribes to all searchParams changes**\n\n```tsx\nfunction ShareButton({ chatId }: { chatId: string }) {\n  const searchParams = useSearchParams()\n\n  const handleShare = () => {\n    const ref = searchParams.get('ref')\n    shareChat(chatId, { ref })\n  }\n\n  return <button onClick={handleShare}>Share</button>\n}\n```\n\n**Correct: reads on demand, no subscription**\n\n```tsx\nfunction ShareButton({ chatId }: { chatId: string }) {\n  const handleShare = () => {\n    const params = new URLSearchParams(window.location.search)\n    const ref = params.get('ref')\n    shareChat(chatId, { ref })\n  }\n\n  return <button onClick={handleShare}>Share</button>\n}\n```\n\n### 5.3 Do not wrap a simple expression with a primitive result type in useMemo\n\n**Impact: LOW-MEDIUM (wasted computation on every render)**\n\nWhen an expression is simple (few logical or arithmetical operators) and has a primitive result type (boolean, number, string), do not wrap it in `useMemo`.\n\nCalling `useMemo` and comparing hook dependencies may consume more resources than the expression itself.\n\n**Incorrect:**\n\n```tsx\nfunction Header({ user, notifications }: Props) {\n  const isLoading = useMemo(() => {\n    return user.isLoading || notifications.isLoading\n  }, [user.isLoading, notifications.isLoading])\n\n  if (isLoading) return <Skeleton />\n  // return some markup\n}\n```\n\n**Correct:**\n\n```tsx\nfunction Header({ user, notifications }: Props) {\n  const isLoading = user.isLoading || notifications.isLoading\n\n  if (isLoading) return <Skeleton />\n  // return some markup\n}\n```\n\n### 5.4 Extract Default Non-primitive Parameter Value from Memoized Component to Constant\n\n**Impact: MEDIUM (restores memoization by using a constant for default value)**\n\nWhen memoized component has a default value for some non-primitive optional parameter, such as an array, function, or object, calling the component without that parameter results in broken memoization. This is because new value instances are created on every rerender, and they do not pass strict equality comparison in `memo()`.\n\nTo address this issue, extract the default value into a constant.\n\n**Incorrect: `onClick` has different values on every rerender**\n\n```tsx\nconst UserAvatar = memo(function UserAvatar({ onClick = () => {} }: { onClick?: () => void }) {\n  // ...\n})\n\n// Used without optional onClick\n<UserAvatar />\n```\n\n**Correct: stable default value**\n\n```tsx\nconst NOOP = () => {};\n\nconst UserAvatar = memo(function UserAvatar({ onClick = NOOP }: { onClick?: () => void }) {\n  // ...\n})\n\n// Used without optional onClick\n<UserAvatar />\n```\n\n### 5.5 Extract to Memoized Components\n\n**Impact: MEDIUM (enables early returns)**\n\nExtract expensive work into memoized components to enable early returns before computation.\n\n**Incorrect: computes avatar even when loading**\n\n```tsx\nfunction Profile({ user, loading }: Props) {\n  const avatar = useMemo(() => {\n    const id = computeAvatarId(user)\n    return <Avatar id={id} />\n  }, [user])\n\n  if (loading) return <Skeleton />\n  return <div>{avatar}</div>\n}\n```\n\n**Correct: skips computation when loading**\n\n```tsx\nconst UserAvatar = memo(function UserAvatar({ user }: { user: User }) {\n  const id = useMemo(() => computeAvatarId(user), [user])\n  return <Avatar id={id} />\n})\n\nfunction Profile({ user, loading }: Props) {\n  if (loading) return <Skeleton />\n  return (\n    <div>\n      <UserAvatar user={user} />\n    </div>\n  )\n}\n```\n\n**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, manual memoization with `memo()` and `useMemo()` is not necessary. The compiler automatically optimizes re-renders.\n\n### 5.6 Narrow Effect Dependencies\n\n**Impact: LOW (minimizes effect re-runs)**\n\nSpecify primitive dependencies instead of objects to minimize effect re-runs.\n\n**Incorrect: re-runs on any user field change**\n\n```tsx\nuseEffect(() => {\n  console.log(user.id)\n}, [user])\n```\n\n**Correct: re-runs only when id changes**\n\n```tsx\nuseEffect(() => {\n  console.log(user.id)\n}, [user.id])\n```\n\n**For derived state, compute outside effect:**\n\n```tsx\n// Incorrect: runs on width=767, 766, 765...\nuseEffect(() => {\n  if (width < 768) {\n    enableMobileMode()\n  }\n}, [width])\n\n// Correct: runs only on boolean transition\nconst isMobile = width < 768\nuseEffect(() => {\n  if (isMobile) {\n    enableMobileMode()\n  }\n}, [isMobile])\n```\n\n### 5.7 Put Interaction Logic in Event Handlers\n\n**Impact: MEDIUM (avoids effect re-runs and duplicate side effects)**\n\nIf a side effect is triggered by a specific user action (submit, click, drag), run it in that event handler. Do not model the action as state + effect; it makes effects re-run on unrelated changes and can duplicate the action.\n\n**Incorrect: event modeled as state + effect**\n\n```tsx\nfunction Form() {\n  const [submitted, setSubmitted] = useState(false)\n  const theme = useContext(ThemeContext)\n\n  useEffect(() => {\n    if (submitted) {\n      post('/api/register')\n      showToast('Registered', theme)\n    }\n  }, [submitted, theme])\n\n  return <button onClick={() => setSubmitted(true)}>Submit</button>\n}\n```\n\n**Correct: do it in the handler**\n\n```tsx\nfunction Form() {\n  const theme = useContext(ThemeContext)\n\n  function handleSubmit() {\n    post('/api/register')\n    showToast('Registered', theme)\n  }\n\n  return <button onClick={handleSubmit}>Submit</button>\n}\n```\n\nReference: [https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler](https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler)\n\n### 5.8 Subscribe to Derived State\n\n**Impact: MEDIUM (reduces re-render frequency)**\n\nSubscribe to derived boolean state instead of continuous values to reduce re-render frequency.\n\n**Incorrect: re-renders on every pixel change**\n\n```tsx\nfunction Sidebar() {\n  const width = useWindowWidth()  // updates continuously\n  const isMobile = width < 768\n  return <nav className={isMobile ? 'mobile' : 'desktop'} />\n}\n```\n\n**Correct: re-renders only when boolean changes**\n\n```tsx\nfunction Sidebar() {\n  const isMobile = useMediaQuery('(max-width: 767px)')\n  return <nav className={isMobile ? 'mobile' : 'desktop'} />\n}\n```\n\n### 5.9 Use Functional setState Updates\n\n**Impact: MEDIUM (prevents stale closures and unnecessary callback recreations)**\n\nWhen updating state based on the current state value, use the functional update form of setState instead of directly referencing the state variable. This prevents stale closures, eliminates unnecessary dependencies, and creates stable callback references.\n\n**Incorrect: requires state as dependency**\n\n```tsx\nfunction TodoList() {\n  const [items, setItems] = useState(initialItems)\n  \n  // Callback must depend on items, recreated on every items change\n  const addItems = useCallback((newItems: Item[]) => {\n    setItems([...items, ...newItems])\n  }, [items])  // ❌ items dependency causes recreations\n  \n  // Risk of stale closure if dependency is forgotten\n  const removeItem = useCallback((id: string) => {\n    setItems(items.filter(item => item.id !== id))\n  }, [])  // ❌ Missing items dependency - will use stale items!\n  \n  return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />\n}\n```\n\nThe first callback is recreated every time `items` changes, which can cause child components to re-render unnecessarily. The second callback has a stale closure bug—it will always reference the initial `items` value.\n\n**Correct: stable callbacks, no stale closures**\n\n```tsx\nfunction TodoList() {\n  const [items, setItems] = useState(initialItems)\n  \n  // Stable callback, never recreated\n  const addItems = useCallback((newItems: Item[]) => {\n    setItems(curr => [...curr, ...newItems])\n  }, [])  // ✅ No dependencies needed\n  \n  // Always uses latest state, no stale closure risk\n  const removeItem = useCallback((id: string) => {\n    setItems(curr => curr.filter(item => item.id !== id))\n  }, [])  // ✅ Safe and stable\n  \n  return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />\n}\n```\n\n**Benefits:**\n\n1. **Stable callback references** - Callbacks don't need to be recreated when state changes\n\n2. **No stale closures** - Always operates on the latest state value\n\n3. **Fewer dependencies** - Simplifies dependency arrays and reduces memory leaks\n\n4. **Prevents bugs** - Eliminates the most common source of React closure bugs\n\n**When to use functional updates:**\n\n- Any setState that depends on the current state value\n\n- Inside useCallback/useMemo when state is needed\n\n- Event handlers that reference state\n\n- Async operations that update state\n\n**When direct updates are fine:**\n\n- Setting state to a static value: `setCount(0)`\n\n- Setting state from props/arguments only: `setName(newName)`\n\n- State doesn't depend on previous value\n\n**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler can automatically optimize some cases, but functional updates are still recommended for correctness and to prevent stale closure bugs.\n\n### 5.10 Use Lazy State Initialization\n\n**Impact: \n\n<!-- [TRUNCATED AT 50KB] -->\n\n<!-- File: .agents/skills/vercel-react-best-practices/README.md (Tokens: ~848 | Category: anthropic-skill) -->\n# React Best Practices\n\nA structured repository for creating and maintaining React Best Practices optimized for agents and LLMs.\n\n## Structure\n\n- `rules/` - Individual rule files (one per rule)\n  - `_sections.md` - Section metadata (titles, impacts, descriptions)\n  - `_template.md` - Template for creating new rules\n  - `area-description.md` - Individual rule files\n- `src/` - Build scripts and utilities\n- `metadata.json` - Document metadata (version, organization, abstract)\n- __`AGENTS.md`__ - Compiled output (generated)\n- __`test-cases.json`__ - Test cases for LLM evaluation (generated)\n\n## Getting Started\n\n1. Install dependencies:\n   ```bash\n   pnpm install\n   ```\n\n2. Build AGENTS.md from rules:\n   ```bash\n   pnpm build\n   ```\n\n3. Validate rule files:\n   ```bash\n   pnpm validate\n   ```\n\n4. Extract test cases:\n   ```bash\n   pnpm extract-tests\n   ```\n\n## Creating a New Rule\n\n1. Copy `rules/_template.md` to `rules/area-description.md`\n2. Choose the appropriate area prefix:\n   - `async-` for Eliminating Waterfalls (Section 1)\n   - `bundle-` for Bundle Size Optimization (Section 2)\n   - `server-` for Server-Side Performance (Section 3)\n   - `client-` for Client-Side Data Fetching (Section 4)\n   - `rerender-` for Re-render Optimization (Section 5)\n   - `rendering-` for Rendering Performance (Section 6)\n   - `js-` for JavaScript Performance (Section 7)\n   - `advanced-` for Advanced Patterns (Section 8)\n3. Fill in the frontmatter and content\n4. Ensure you have clear examples with explanations\n5. Run `pnpm build` to regenerate AGENTS.md and test-cases.json\n\n## Rule File Structure\n\nEach rule file should follow this structure:\n\n```markdown\n---\ntitle: Rule Title Here\nimpact: MEDIUM\nimpactDescription: Optional description\ntags: tag1, tag2, tag3\n---\n\n## Rule Title Here\n\nBrief explanation of the rule and why it matters.\n\n**Incorrect (description of what's wrong):**\n\n```typescript\n// Bad code example\n```\n\n**Correct (description of what's right):**\n\n```typescript\n// Good code example\n```\n\nOptional explanatory text after examples.\n\nReference: [Link](https://example.com)\n\n## File Naming Convention\n\n- Files starting with `_` are special (excluded from build)\n- Rule files: `area-description.md` (e.g., `async-parallel.md`)\n- Section is automatically inferred from filename prefix\n- Rules are sorted alphabetically by title within each section\n- IDs (e.g., 1.1, 1.2) are auto-generated during build\n\n## Impact Levels\n\n- `CRITICAL` - Highest priority, major performance gains\n- `HIGH` - Significant performance improvements\n- `MEDIUM-HIGH` - Moderate-high gains\n- `MEDIUM` - Moderate performance improvements\n- `LOW-MEDIUM` - Low-medium gains\n- `LOW` - Incremental improvements\n\n## Scripts\n\n- `pnpm build` - Compile rules into AGENTS.md\n- `pnpm validate` - Validate all rule files\n- `pnpm extract-tests` - Extract test cases for LLM evaluation\n- `pnpm dev` - Build and validate\n\n## Contributing\n\nWhen adding or modifying rules:\n\n1. Use the correct filename prefix for your section\n2. Follow the `_template.md` structure\n3. Include clear bad/good examples with explanations\n4. Add appropriate tags\n5. Run `pnpm build` to regenerate AGENTS.md and test-cases.json\n6. Rules are automatically sorted by title - no need to manage numbers!\n\n## Acknowledgments\n\nOriginally created by [@shuding](https://x.com/shuding) at [Vercel](https://vercel.com).\n\n<!-- File: .agents/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md (Tokens: ~337 | Category: anthropic-skill) -->\n---\ntitle: Store Event Handlers in Refs\nimpact: LOW\nimpactDescription: stable subscriptions\ntags: advanced, hooks, refs, event-handlers, optimization\n---\n\n## Store Event Handlers in Refs\n\nStore callbacks in refs when used in effects that shouldn't re-subscribe on callback changes.\n\n**Incorrect (re-subscribes on every render):**\n\n```tsx\nfunction useWindowEvent(event: string, handler: (e) => void) {\n  useEffect(() => {\n    window.addEventListener(event, handler)\n    return () => window.removeEventListener(event, handler)\n  }, [event, handler])\n}\n```\n\n**Correct (stable subscription):**\n\n```tsx\nfunction useWindowEvent(event: string, handler: (e) => void) {\n  const handlerRef = useRef(handler)\n  useEffect(() => {\n    handlerRef.current = handler\n  }, [handler])\n\n  useEffect(() => {\n    const listener = (e) => handlerRef.current(e)\n    window.addEventListener(event, listener)\n    return () => window.removeEventListener(event, listener)\n  }, [event])\n}\n```\n\n**Alternative: use `useEffectEvent` if you're on latest React:**\n\n```tsx\nimport { useEffectEvent } from 'react'\n\nfunction useWindowEvent(event: string, handler: (e) => void) {\n  const onEvent = useEffectEvent(handler)\n\n  useEffect(() => {\n    window.addEventListener(event, onEvent)\n    return () => window.removeEventListener(event, onEvent)\n  }, [event])\n}\n```\n\n`useEffectEvent` provides a cleaner API for the same pattern: it creates a stable function reference that always calls the latest version of the handler.\n\n<!-- File: .agents/skills/vercel-react-best-practices/rules/advanced-init-once.md (Tokens: ~230 | Category: anthropic-skill) -->\n---\ntitle: Initialize App Once, Not Per Mount\nimpact: LOW-MEDIUM\nimpactDescription: avoids duplicate init in development\ntags: initialization, useEffect, app-startup, side-effects\n---\n\n## Initialize App Once, Not Per Mount\n\nDo not put app-wide initialization that must run once per app load inside `useEffect([])` of a component. Components can remount and effects will re-run. Use a module-level guard or top-level init in the entry module instead.\n\n**Incorrect (runs twice in dev, re-runs on remount):**\n\n```tsx\nfunction Comp() {\n  useEffect(() => {\n    loadFromStorage()\n    checkAuthToken()\n  }, [])\n\n  // ...\n}\n```\n\n**Correct (once per app load):**\n\n```tsx\nlet didInit = false\n\nfunction Comp() {\n  useEffect(() => {\n    if (didInit) return\n    didInit = true\n    loadFromStorage()\n    checkAuthToken()\n  }, [])\n\n  // ...\n}\n```\n\nReference: [Initializing the application](https://react.dev/learn/you-might-not-need-an-effect#initializing-the-application)\n\n<!-- File: .agents/skills/vercel-react-best-practices/rules/advanced-use-latest.md (Tokens: ~254 | Category: anthropic-skill) -->\n---\ntitle: useEffectEvent for Stable Callback Refs\nimpact: LOW\nimpactDescription: prevents effect re-runs\ntags: advanced, hooks, useEffectEvent, refs, optimization\n---\n\n## useEffectEvent for Stable Callback Refs\n\nAccess latest values in callbacks without adding them to dependency arrays. Prevents effect re-runs while avoiding stale closures.\n\n**Incorrect (effect re-runs on every callback change):**\n\n```tsx\nfunction SearchInput({ onSearch }: { onSearch: (q: string) => void }) {\n  const [query, setQuery] = useState('')\n\n  useEffect(() => {\n    const timeout = setTimeout(() => onSearch(query), 300)\n    return () => clearTimeout(timeout)\n  }, [query, onSearch])\n}\n```\n\n**Correct (using React's useEffectEvent):**\n\n```tsx\nimport { useEffectEvent } from 'react';\n\nfunction SearchInput({ onSearch }: { onSearch: (q: string) => void }) {\n  const [query, setQuery] = useState('')\n  const onSearchEvent = useEffectEvent(onSearch)\n\n  useEffect(() => {\n    const timeout = setTimeout(() => onSearchEvent(query), 300)\n    return () => clearTimeout(timeout)\n  }, [query])\n}\n```\n\n<!-- File: .agents/skills/vercel-react-best-practices/rules/async-api-routes.md (Tokens: ~241 | Category: anthropic-skill) -->\n---\ntitle: Prevent Waterfall Chains in API Routes\nimpact: CRITICAL\nimpactDescription: 2-10× improvement\ntags: api-routes, server-actions, waterfalls, parallelization\n---\n\n## Prevent Waterfall Chains in API Routes\n\nIn API routes and Server Actions, start independent operations immediately, even if you don't await them yet.\n\n**Incorrect (config waits for auth, data waits for both):**\n\n```typescript\nexport async function GET(request: Request) {\n  const session = await auth()\n  const config = await fetchConfig()\n  const data = await fetchData(session.user.id)\n  return Response.json({ data, config })\n}\n```\n\n**Correct (auth and config start immediately):**\n\n```typescript\nexport async function GET(request: Request) {\n  const sessionPromise = auth()\n  const configPromise = fetchConfig()\n  const session = await sessionPromise\n  const [config, data] = await Promise.all([\n    configPromise,\n    fetchData(session.user.id)\n  ])\n  return Response.json({ data, config })\n}\n```\n\nFor operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization).\n\n<!-- File: .agents/skills/vercel-react-best-practices/rules/async-defer-await.md (Tokens: ~437 | Category: anthropic-skill) -->\n---\ntitle: Defer Await Until Needed\nimpact: HIGH\nimpactDescription: avoids blocking unused code paths\ntags: async, await, conditional, optimization\n---\n\n## Defer Await Until Needed\n\nMove `await` operations into the branches where they're actually used to avoid blocking code paths that don't need them.\n\n**Incorrect (blocks both branches):**\n\n```typescript\nasync function handleRequest(userId: string, skipProcessing: boolean) {\n  const userData = await fetchUserData(userId)\n  \n  if (skipProcessing) {\n    // Returns immediately but still waited for userData\n    return { skipped: true }\n  }\n  \n  // Only this branch uses userData\n  return processUserData(userData)\n}\n```\n\n**Correct (only blocks when needed):**\n\n```typescript\nasync function handleRequest(userId: string, skipProcessing: boolean) {\n  if (skipProcessing) {\n    // Returns immediately without waiting\n    return { skipped: true }\n  }\n  \n  // Fetch only when needed\n  const userData = await fetchUserData(userId)\n  return processUserData(userData)\n}\n```\n\n**Another example (early return optimization):**\n\n```typescript\n// Incorrect: always fetches permissions\nasync function updateResource(resourceId: string, userId: string) {\n  const permissions = await fetchPermissions(userId)\n  const resource = await getResource(resourceId)\n  \n  if (!resource) {\n    return { error: 'Not found' }\n  }\n  \n  if (!permissions.canEdit) {\n    return { error: 'Forbidden' }\n  }\n  \n  return await updateResourceData(resource, permissions)\n}\n\n// Correct: fetches only when needed\nasync function updateResource(resourceId: string, userId: string) {\n  const resource = await getResource(resourceId)\n  \n  if (!resource) {\n    return { error: 'Not found' }\n  }\n  \n  const permissions = await fetchPermissions(userId)\n  \n  if (!permissions.canEdit) {\n    return { error: 'Forbidden' }\n  }\n  \n  return await updateResourceData(resource, permissions)\n}\n```\n\nThis optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive.\n\n<!-- File: .agents/skills/vercel-react-best-practices/rules/async-dependencies.md (Tokens: ~291 | Category: anthropic-skill) -->\n---\ntitle: Dependency-Based Parallelization\nimpact: CRITICAL\nimpactDescription: 2-10× improvement\ntags: async, parallelization, dependencies, better-all\n---\n\n## Dependency-Based Parallelization\n\nFor operations with partial dependencies, use `better-all` to maximize parallelism. It automatically starts each task at the earliest possible moment.\n\n**Incorrect (profile waits for config unnecessarily):**\n\n```typescript\nconst [user, config] = await Promise.all([\n  fetchUser(),\n  fetchConfig()\n])\nconst profile = await fetchProfile(user.id)\n```\n\n**Correct (config and profile run in parallel):**\n\n```typescript\nimport { all } from 'better-all'\n\nconst { user, config, profile } = await all({\n  async user() { return fetchUser() },\n  async config() { return fetchConfig() },\n  async profile() {\n    return fetchProfile((await this.$.user).id)\n  }\n})\n```\n\n**Alternative without extra dependencies:**\n\nWe can also create all the promises first, and do `Promise.all()` at the end.\n\n```typescript\nconst userPromise = fetchUser()\nconst profilePromise = userPromise.then(user => fetchProfile(user.id))\n\nconst [user, config, profile] = await Promise.all([\n  userPromise,\n  fetchConfig(),\n  profilePromise\n])\n```\n\nReference: [https://github.com/shuding/better-all](https://github.com/shuding/better-all)\n\n<!-- File: .agents/skills/vercel-react-best-practices/rules/async-parallel.md (Tokens: ~144 | Category: anthropic-skill) -->\n---\ntitle: Promise.all() for Independent Operations\nimpact: CRITICAL\nimpactDescription: 2-10× improvement\ntags: async, parallelization, promises, waterfalls\n---\n\n## Promise.all() for Independent Operations\n\nWhen async operations have no interdependencies, execute them concurrently using `Promise.all()`.\n\n**Incorrect (sequential execution, 3 round trips):**\n\n```typescript\nconst user = await fetchUser()\nconst posts = await fetchPosts()\nconst comments = await fetchComments()\n```\n\n**Correct (parallel execution, 1 round trip):**\n\n```typescript\nconst [user, posts, comments] = await Promise.all([\n  fetchUser(),\n  fetchPosts(),\n  fetchComments()\n])\n```\n\n<!-- ... +60 more modular skills available via /api/skills/vercel-labs/open-agents?path={path} -->\n</yakaai_skills>"}