{"owner":"JimmyLv","repo":"BibiGPT-v1","hasSkills":true,"totalSkillsCount":98,"totalTokensCount":76524,"categories":["anthropic-skill","root-instruction"],"hasMcp":false,"mcpConfig":null,"found":[".agents/skills/agent-browser/SKILL.md",".agents/skills/agent-browser/references/authentication.md",".agents/skills/agent-browser/references/commands.md",".agents/skills/agent-browser/references/profiling.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/bibi/SKILL.md",".agents/skills/find-skills/SKILL.md",".agents/skills/next-best-practices/SKILL.md",".agents/skills/next-best-practices/async-patterns.md",".agents/skills/next-best-practices/bundling.md",".agents/skills/next-best-practices/data-patterns.md",".agents/skills/next-best-practices/debug-tricks.md",".agents/skills/next-best-practices/directives.md",".agents/skills/next-best-practices/error-handling.md",".agents/skills/next-best-practices/file-conventions.md",".agents/skills/next-best-practices/font.md",".agents/skills/next-best-practices/functions.md",".agents/skills/next-best-practices/hydration-error.md",".agents/skills/next-best-practices/image.md",".agents/skills/next-best-practices/metadata.md",".agents/skills/next-best-practices/parallel-routes.md",".agents/skills/next-best-practices/route-handlers.md",".agents/skills/next-best-practices/rsc-boundaries.md",".agents/skills/next-best-practices/runtime-selection.md",".agents/skills/next-best-practices/scripts.md",".agents/skills/next-best-practices/self-hosting.md",".agents/skills/next-best-practices/suspense-boundaries.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-design-guidelines/SKILL.md","AGENTS.md"],"skills":{".agents/skills/agent-browser/SKILL.md":"---\nname: agent-browser\ndescription: Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to \"open a website\", \"fill out a form\", \"click a button\", \"take a screenshot\", \"scrape data from a page\", \"test this web app\", \"login to a site\", \"automate browser actions\", or any task requiring programmatic web interaction.\nallowed-tools: Bash(npx agent-browser:*), Bash(agent-browser:*)\n---\n\n# Browser Automation with agent-browser\n\n## Core Workflow\n\nEvery browser automation follows this pattern:\n\n1. **Navigate**: `agent-browser open <url>`\n2. **Snapshot**: `agent-browser snapshot -i` (get element refs like `@e1`, `@e2`)\n3. **Interact**: Use refs to click, fill, select\n4. **Re-snapshot**: After navigation or DOM changes, get fresh refs\n\n```bash\nagent-browser open https://example.com/form\nagent-browser snapshot -i\n# Output: @e1 [input type=\"email\"], @e2 [input type=\"password\"], @e3 [button] \"Submit\"\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## Command Chaining\n\nCommands can be chained with `&&` in a single shell invocation. The browser persists between commands via a background daemon, so chaining is safe and more efficient than separate calls.\n\n```bash\n# Chain open + wait + snapshot in one call\nagent-browser open https://example.com && agent-browser wait --load networkidle && agent-browser snapshot -i\n\n# Chain multiple interactions\nagent-browser fill @e1 \"user@example.com\" && agent-browser fill @e2 \"password123\" && agent-browser click @e3\n\n# Navigate and capture\nagent-browser open https://example.com && agent-browser wait --load networkidle && agent-browser screenshot page.png\n```\n\n**When to chain:** Use `&&` when you don't need to read the output of an intermediate command before proceeding (e.g., open + wait + screenshot). Run commands separately when you need to parse the output first (e.g., snapshot to discover refs, then interact using those refs).\n\n## Essential Commands\n\n```bash\n# Navigation\nagent-browser open <url>              # Navigate (aliases: goto, navigate)\nagent-browser close                   # Close browser\n\n# Snapshot\nagent-browser snapshot -i             # Interactive elements with refs (recommended)\nagent-browser snapshot -i -C          # Include cursor-interactive elements (divs with onclick, cursor:pointer)\nagent-browser snapshot -s \"#selector\" # Scope to CSS selector\n\n# Interaction (use @refs from snapshot)\nagent-browser click @e1               # Click element\nagent-browser click @e1 --new-tab     # Click and open in new tab\nagent-browser fill @e2 \"text\"         # Clear and type text\nagent-browser type @e2 \"text\"         # Type without clearing\nagent-browser select @e1 \"option\"     # Select dropdown option\nagent-browser check @e1               # Check checkbox\nagent-browser press Enter             # Press key\nagent-browser keyboard type \"text\"    # Type at current focus (no selector)\nagent-browser keyboard inserttext \"text\"  # Insert without key events\nagent-browser scroll down 500         # Scroll page\nagent-browser scroll down 500 --selector \"div.content\"  # Scroll within a specific container\n\n# Get information\nagent-browser get text @e1            # Get element text\nagent-browser get url                 # Get current URL\nagent-browser get title               # Get page title\n\n# Wait\nagent-browser wait @e1                # Wait for element\nagent-browser wait --load networkidle # Wait for network idle\nagent-browser wait --url \"**/page\"    # Wait for URL pattern\nagent-browser wait 2000               # Wait milliseconds\n\n# Downloads\nagent-browser download @e1 ./file.pdf          # Click element to trigger download\nagent-browser wait --download ./output.zip     # Wait for any download to complete\nagent-browser --download-path ./downloads open <url>  # Set default download directory\n\n# Capture\nagent-browser screenshot              # Screenshot to temp dir\nagent-browser screenshot --full       # Full page screenshot\nagent-browser screenshot --annotate   # Annotated screenshot with numbered element labels\nagent-browser pdf output.pdf          # Save as PDF\n\n# Diff (compare page states)\nagent-browser diff snapshot                          # Compare current vs last snapshot\nagent-browser diff snapshot --baseline before.txt    # Compare current vs saved file\nagent-browser diff screenshot --baseline before.png  # Visual pixel diff\nagent-browser diff url <url1> <url2>                 # Compare two pages\nagent-browser diff url <url1> <url2> --wait-until networkidle  # Custom wait strategy\nagent-browser diff url <url1> <url2> --selector \"#main\"  # Scope to element\n```\n\n## Common Patterns\n\n### Form Submission\n\n```bash\nagent-browser open https://example.com/signup\nagent-browser snapshot -i\nagent-browser fill @e1 \"Jane Doe\"\nagent-browser fill @e2 \"jane@example.com\"\nagent-browser select @e3 \"California\"\nagent-browser check @e4\nagent-browser click @e5\nagent-browser wait --load networkidle\n```\n\n### Authentication with Auth Vault (Recommended)\n\n```bash\n# Save credentials once (encrypted with AGENT_BROWSER_ENCRYPTION_KEY)\n# Recommended: pipe password via stdin to avoid shell history exposure\necho \"pass\" | agent-browser auth save github --url https://github.com/login --username user --password-stdin\n\n# Login using saved profile (LLM never sees password)\nagent-browser auth login github\n\n# List/show/delete profiles\nagent-browser auth list\nagent-browser auth show github\nagent-browser auth delete github\n```\n\n### Authentication with State Persistence\n\n```bash\n# Login once and save state\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# Reuse in future sessions\nagent-browser state load auth.json\nagent-browser open https://app.example.com/dashboard\n```\n\n### Session Persistence\n\n```bash\n# Auto-save/restore cookies and localStorage across browser restarts\nagent-browser --session-name myapp open https://app.example.com/login\n# ... login flow ...\nagent-browser close  # State auto-saved to ~/.agent-browser/sessions/\n\n# Next time, state is auto-loaded\nagent-browser --session-name myapp open https://app.example.com/dashboard\n\n# Encrypt state at rest\nexport AGENT_BROWSER_ENCRYPTION_KEY=$(openssl rand -hex 32)\nagent-browser --session-name secure open https://app.example.com\n\n# Manage saved states\nagent-browser state list\nagent-browser state show myapp-default.json\nagent-browser state clear myapp\nagent-browser state clean --older-than 7\n```\n\n### Data Extraction\n\n```bash\nagent-browser open https://example.com/products\nagent-browser snapshot -i\nagent-browser get text @e5           # Get specific element text\nagent-browser get text body > page.txt  # Get all page text\n\n# JSON output for parsing\nagent-browser snapshot -i --json\nagent-browser get text @e1 --json\n```\n\n### Parallel Sessions\n\n```bash\nagent-browser --session site1 open https://site-a.com\nagent-browser --session site2 open https://site-b.com\n\nagent-browser --session site1 snapshot -i\nagent-browser --session site2 snapshot -i\n\nagent-browser session list\n```\n\n### Connect to Existing Chrome\n\n```bash\n# Auto-discover running Chrome with remote debugging enabled\nagent-browser --auto-connect open https://example.com\nagent-browser --auto-connect snapshot\n\n# Or with explicit CDP port\nagent-browser --cdp 9222 snapshot\n```\n\n### Color Scheme (Dark Mode)\n\n```bash\n# Persistent dark mode via flag (applies to all pages and new tabs)\nagent-browser --color-scheme dark open https://example.com\n\n# Or via environment variable\nAGENT_BROWSER_COLOR_SCHEME=dark agent-browser open https://example.com\n\n# Or set during session (persists for subsequent commands)\nagent-browser set media dark\n```\n\n### Visual Browser (Debugging)\n\n```bash\nagent-browser --headed open https://example.com\nagent-browser highlight @e1          # Highlight element\nagent-browser record start demo.webm # Record session\nagent-browser profiler start         # Start Chrome DevTools profiling\nagent-browser profiler stop trace.json # Stop and save profile (path optional)\n```\n\nUse `AGENT_BROWSER_HEADED=1` to enable headed mode via environment variable. Browser extensions work in both headed and headless mode.\n\n### Local Files (PDFs, HTML)\n\n```bash\n# Open local files with file:// URLs\nagent-browser --allow-file-access open file:///path/to/document.pdf\nagent-browser --allow-file-access open file:///path/to/page.html\nagent-browser screenshot output.png\n```\n\n### iOS Simulator (Mobile Safari)\n\n```bash\n# List available iOS simulators\nagent-browser device list\n\n# Launch Safari on a specific device\nagent-browser -p ios --device \"iPhone 16 Pro\" open https://example.com\n\n# Same workflow as desktop - snapshot, interact, re-snapshot\nagent-browser -p ios snapshot -i\nagent-browser -p ios tap @e1          # Tap (alias for click)\nagent-browser -p ios fill @e2 \"text\"\nagent-browser -p ios swipe up         # Mobile-specific gesture\n\n# Take screenshot\nagent-browser -p ios screenshot mobile.png\n\n# Close session (shuts down simulator)\nagent-browser -p ios close\n```\n\n**Requirements:** macOS with Xcode, Appium (`npm install -g appium && appium driver install xcuitest`)\n\n**Real devices:** Works with physical iOS devices if pre-configured. Use `--device \"<UDID>\"` where UDID is from `xcrun xctrace list devices`.\n\n## Security\n\nAll security features are opt-in. By default, agent-browser imposes no restrictions on navigation, actions, or output.\n\n### Content Boundaries (Recommended for AI Agents)\n\nEnable `--content-boundaries` to wrap page-sourced output in markers that help LLMs distinguish tool output from untrusted page content:\n\n```bash\nexport AGENT_BROWSER_CONTENT_BOUNDARIES=1\nagent-browser snapshot\n# Output:\n# --- AGENT_BROWSER_PAGE_CONTENT nonce=<hex> origin=https://example.com ---\n# [accessibility tree]\n# --- END_AGENT_BROWSER_PAGE_CONTENT nonce=<hex> ---\n```\n\n### Domain Allowlist\n\nRestrict navigation to trusted domains. Wildcards like `*.example.com` also match the bare domain `example.com`. Sub-resource requests, WebSocket, and EventSource connections to non-allowed domains are also blocked. Include CDN domains your target pages depend on:\n\n```bash\nexport AGENT_BROWSER_ALLOWED_DOMAINS=\"example.com,*.example.com\"\nagent-browser open https://example.com        # OK\nagent-browser open https://malicious.com       # Blocked\n```\n\n### Action Policy\n\nUse a policy file to gate destructive actions:\n\n```bash\nexport AGENT_BROWSER_ACTION_POLICY=./policy.json\n```\n\nExample `policy.json`:\n\n```json\n{ \"default\": \"deny\", \"allow\": [\"navigate\", \"snapshot\", \"click\", \"scroll\", \"wait\", \"get\"] }\n```\n\nAuth vault operations (`auth login`, etc.) bypass action policy but domain allowlist still applies.\n\n### Output Limits\n\nPrevent context flooding from large pages:\n\n```bash\nexport AGENT_BROWSER_MAX_OUTPUT=50000\n```\n\n## Diffing (Verifying Changes)\n\nUse `diff snapshot` after performing an action to verify it had the intended effect. This compares the current accessibility tree against the last snapshot taken in the session.\n\n```bash\n# Typical workflow: snapshot -> action -> diff\nagent-browser snapshot -i          # Take baseline snapshot\nagent-browser click @e2            # Perform action\nagent-browser diff snapshot        # See what changed (auto-compares to last snapshot)\n```\n\nFor visual regression testing or monitoring:\n\n```bash\n# Save a baseline screenshot, then compare later\nagent-browser screenshot baseline.png\n# ... time passes or changes are made ...\nagent-browser diff screenshot --baseline baseline.png\n\n# Compare staging vs production\nagent-browser diff url https://staging.example.com https://prod.example.com --screenshot\n```\n\n`diff snapshot` output uses `+` for additions and `-` for removals, similar to git diff. `diff screenshot` produces a diff image with changed pixels highlighted in red, plus a mismatch percentage.\n\n## Timeouts and Slow Pages\n\nThe default Playwright timeout is 25 seconds for local browsers. This can be overridden with the `AGENT_BROWSER_DEFAULT_TIMEOUT` environment variable (value in milliseconds). For slow websites or large pages, use explicit waits instead of relying on the default timeout:\n\n```bash\n# Wait for network activity to settle (best for slow pages)\nagent-browser wait --load networkidle\n\n# Wait for a specific element to appear\nagent-browser wait \"#content\"\nagent-browser wait @e1\n\n# Wait for a specific URL pattern (useful after redirects)\nagent-browser wait --url \"**/dashboard\"\n\n# Wait for a JavaScript condition\nagent-browser wait --fn \"document.readyState === 'complete'\"\n\n# Wait a fixed duration (milliseconds) as a last resort\nagent-browser wait 5000\n```\n\nWhen dealing with consistently slow websites, use `wait --load networkidle` after `open` to ensure the page is fully loaded before taking a snapshot. If a specific element is slow to render, wait for it directly with `wait <selector>` or `wait @ref`.\n\n## Session Management and Cleanup\n\nWhen running multiple agents or automations concurrently, always use named sessions to avoid conflicts:\n\n```bash\n# Each agent gets its own isolated session\nagent-browser --session agent1 open site-a.com\nagent-browser --session agent2 open site-b.com\n\n# Check active sessions\nagent-browser session list\n```\n\nAlways close your browser session when done to avoid leaked processes:\n\n```bash\nagent-browser close                    # Close default session\nagent-browser --session agent1 close   # Close specific session\n```\n\nIf a previous session was not closed properly, the daemon may still be running. Use `agent-browser close` to clean it up before starting new work.\n\n## Ref Lifecycle (Important)\n\nRefs (`@e1`, `@e2`, etc.) are invalidated when the page changes. Always re-snapshot after:\n\n- Clicking links or buttons that navigate\n- Form submissions\n- Dynamic content loading (dropdowns, modals)\n\n```bash\nagent-browser click @e5              # Navigates to new page\nagent-browser snapshot -i            # MUST re-snapshot\nagent-browser click @e1              # Use new refs\n```\n\n## Annotated Screenshots (Vision Mode)\n\nUse `--annotate` to take a screenshot with numbered labels overlaid on interactive elements. Each label `[N]` maps to ref `@eN`. This also caches refs, so you can interact with elements immediately without a separate snapshot.\n\n```bash\nagent-browser screenshot --annotate\n# Output includes the image path and a legend:\n#   [1] @e1 button \"Submit\"\n#   [2] @e2 link \"Home\"\n#   [3] @e3 textbox \"Email\"\nagent-browser click @e2              # Click using ref from annotated screenshot\n```\n\nUse annotated screenshots when:\n\n- The page has unlabeled icon buttons or visual-only elements\n- You need to verify visual layout or styling\n- Canvas or chart elements are present (invisible to text snapshots)\n- You need spatial reasoning about element positions\n\n## Semantic Locators (Alternative to Refs)\n\nWhen refs are unavailable or unreliable, use semantic locators:\n\n```bash\nagent-browser find text \"Sign In\" click\nagent-browser find label \"Email\" fill \"user@test.com\"\nagent-browser find role button click --name \"Submit\"\nagent-browser find placeholder \"Search\" type \"query\"\nagent-browser find testid \"submit-btn\" click\n```\n\n## JavaScript Evaluation (eval)\n\nUse `eval` to run JavaScript in the browser context. **Shell quoting can corrupt complex expressions** -- use `--stdin` or `-b` to avoid issues.\n\n```bash\n# Simple expressions work with regular quoting\nagent-browser eval 'document.title'\nagent-browser eval 'document.querySelectorAll(\"img\").length'\n\n# Complex JS: use --stdin with heredoc (RECOMMENDED)\nagent-browser eval --stdin <<'EVALEOF'\nJSON.stringify(\n  Array.from(document.querySelectorAll(\"img\"))\n    .filter(i => !i.alt)\n    .map(i => ({ src: i.src.split(\"/\").pop(), width: i.width }))\n)\nEVALEOF\n\n# Alternative: base64 encoding (avoids all shell escaping issues)\nagent-browser eval -b \"$(echo -n 'Array.from(document.querySelectorAll(\"a\")).map(a => a.href)' | base64)\"\n```\n\n**Why this matters:** When the shell processes your command, inner double quotes, `!` characters (history expansion), backticks, and `$()` can all corrupt the JavaScript before it reaches agent-browser. The `--stdin` and `-b` flags bypass shell interpretation entirely.\n\n**Rules of thumb:**\n\n- Single-line, no nested quotes -> regular `eval 'expression'` with single quotes is fine\n- Nested quotes, arrow functions, template literals, or multiline -> use `eval --stdin <<'EVALEOF'`\n- Programmatic/generated scripts -> use `eval -b` with base64\n\n## Configuration File\n\nCreate `agent-browser.json` in the project root for persistent settings:\n\n```json\n{\n  \"headed\": true,\n  \"proxy\": \"http://localhost:8080\",\n  \"profile\": \"./browser-data\"\n}\n```\n\nPriority (lowest to highest): `~/.agent-browser/config.json` < `./agent-browser.json` < env vars < CLI flags. Use `--config <path>` or `AGENT_BROWSER_CONFIG` env var for a custom config file (exits with error if missing/invalid). All CLI options map to camelCase keys (e.g., `--executable-path` -> `\"executablePath\"`). Boolean flags accept `true`/`false` values (e.g., `--headed false` overrides config). Extensions from user and project configs are merged, not replaced.\n\n## Deep-Dive Documentation\n\n| Reference                                                            | When to Use                                               |\n| -------------------------------------------------------------------- | --------------------------------------------------------- |\n| [references/commands.md](references/commands.md)                     | Full command reference with all options                   |\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/profiling.md](references/profiling.md)                   | Chrome DevTools profiling for performance analysis        |\n| [references/proxy-support.md](references/proxy-support.md)           | Proxy configuration, geo-testing, rotating proxies        |\n\n## Experimental: Native Mode\n\nagent-browser has an experimental native Rust daemon that communicates with Chrome directly via CDP, bypassing Node.js and Playwright entirely. It is opt-in and not recommended for production use yet.\n\n```bash\n# Enable via flag\nagent-browser --native open example.com\n\n# Enable via environment variable (avoids passing --native every time)\nexport AGENT_BROWSER_NATIVE=1\nagent-browser open example.com\n```\n\nThe native daemon supports Chromium and Safari (via WebDriver). Firefox and WebKit are not yet supported. All core commands (navigate, snapshot, click, fill, screenshot, cookies, storage, tabs, eval, etc.) work identically in native mode. Use `agent-browser close` before switching between native and default mode within the same session.\n\n## Ready-to-Use Templates\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\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",".agents/skills/agent-browser/references/authentication.md":"# Authentication Patterns\n\nLogin flows, session persistence, OAuth, 2FA, and authenticated browsing.\n\n**Related**: [session-management.md](session-management.md) for state persistence details, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [Basic Login Flow](#basic-login-flow)\n- [Saving Authentication State](#saving-authentication-state)\n- [Restoring Authentication](#restoring-authentication)\n- [OAuth / SSO Flows](#oauth--sso-flows)\n- [Two-Factor Authentication](#two-factor-authentication)\n- [HTTP Basic Auth](#http-basic-auth)\n- [Cookie-Based Auth](#cookie-based-auth)\n- [Token Refresh Handling](#token-refresh-handling)\n- [Security Best Practices](#security-best-practices)\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\n   ```bash\n   echo \"*.auth-state.json\" >> .gitignore\n   ```\n\n2. **Use environment variables for credentials**\n\n   ```bash\n   agent-browser fill @e1 \"$APP_USERNAME\"\n   agent-browser fill @e2 \"$APP_PASSWORD\"\n   ```\n\n3. **Clean up after automation**\n\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/commands.md":"# Command Reference\n\nComplete reference for all agent-browser commands. For quick start and common patterns, see SKILL.md.\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 click @e1 --new-tab # Click and open in new tab\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 and PDF\n\n```bash\nagent-browser screenshot          # Save to temporary directory\nagent-browser screenshot path.png # Save to 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\nagent-browser click @e1                   # Perform actions\nagent-browser record stop                 # Stop and save video\nagent-browser record restart ./take2.webm # Stop current + start new\n```\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 and 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 and 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\"          # Simple expressions only\nagent-browser eval -b \"<base64>\"             # Any JavaScript (base64 encoded)\nagent-browser eval --stdin                   # Read script from stdin\n```\n\nUse `-b`/`--base64` or `--stdin` for reliable execution. Shell escaping with nested quotes and special characters is error-prone.\n\n```bash\n# Base64 encode your script, then:\nagent-browser eval -b \"ZG9jdW1lbnQucXVlcnlTZWxlY3RvcignW3NyYyo9Il9uZXh0Il0nKQ==\"\n\n# Or use stdin with heredoc for multiline scripts:\ncat <<'EOF' | agent-browser eval --stdin\nconst links = document.querySelectorAll('a');\nArray.from(links).map(a => a.href);\nEOF\n```\n\n## State Management\n\n```bash\nagent-browser state save auth.json    # Save cookies, storage, auth state\nagent-browser state load auth.json    # Restore saved state\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 --proxy-bypass <hosts>  # Hosts to bypass proxy\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 --ignore-https-errors   # Ignore SSL certificate errors\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## 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 profiler start              # Start Chrome DevTools profiling\nagent-browser profiler stop trace.json    # Stop and save profile\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=\"browserbase\"         # Cloud browser provider\nAGENT_BROWSER_STREAM_PORT=\"9223\"             # WebSocket streaming port\nAGENT_BROWSER_HOME=\"/path/to/agent-browser\"  # Custom install location\n```\n",".agents/skills/agent-browser/references/profiling.md":"# Profiling\n\nCapture Chrome DevTools performance profiles during browser automation for performance analysis.\n\n**Related**: [commands.md](commands.md) for full command reference, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [Basic Profiling](#basic-profiling)\n- [Profiler Commands](#profiler-commands)\n- [Categories](#categories)\n- [Use Cases](#use-cases)\n- [Output Format](#output-format)\n- [Viewing Profiles](#viewing-profiles)\n- [Limitations](#limitations)\n\n## Basic Profiling\n\n```bash\n# Start profiling\nagent-browser profiler start\n\n# Perform actions\nagent-browser navigate https://example.com\nagent-browser click \"#button\"\nagent-browser wait 1000\n\n# Stop and save\nagent-browser profiler stop ./trace.json\n```\n\n## Profiler Commands\n\n```bash\n# Start profiling with default categories\nagent-browser profiler start\n\n# Start with custom trace categories\nagent-browser profiler start --categories \"devtools.timeline,v8.execute,blink.user_timing\"\n\n# Stop profiling and save to file\nagent-browser profiler stop ./trace.json\n```\n\n## Categories\n\nThe `--categories` flag accepts a comma-separated list of Chrome trace categories. Default categories include:\n\n- `devtools.timeline` -- standard DevTools performance traces\n- `v8.execute` -- time spent running JavaScript\n- `blink` -- renderer events\n- `blink.user_timing` -- `performance.mark()` / `performance.measure()` calls\n- `latencyInfo` -- input-to-latency tracking\n- `renderer.scheduler` -- task scheduling and execution\n- `toplevel` -- broad-spectrum basic events\n\nSeveral `disabled-by-default-*` categories are also included for detailed timeline, call stack, and V8 CPU profiling data.\n\n## Use Cases\n\n### Diagnosing Slow Page Loads\n\n```bash\nagent-browser profiler start\nagent-browser navigate https://app.example.com\nagent-browser wait --load networkidle\nagent-browser profiler stop ./page-load-profile.json\n```\n\n### Profiling User Interactions\n\n```bash\nagent-browser navigate https://app.example.com\nagent-browser profiler start\nagent-browser click \"#submit\"\nagent-browser wait 2000\nagent-browser profiler stop ./interaction-profile.json\n```\n\n### CI Performance Regression Checks\n\n```bash\n#!/bin/bash\nagent-browser profiler start\nagent-browser navigate https://app.example.com\nagent-browser wait --load networkidle\nagent-browser profiler stop \"./profiles/build-${BUILD_ID}.json\"\n```\n\n## Output Format\n\nThe output is a JSON file in Chrome Trace Event format:\n\n```json\n{\n  \"traceEvents\": [\n    { \"cat\": \"devtools.timeline\", \"name\": \"RunTask\", \"ph\": \"X\", \"ts\": 12345, \"dur\": 100, ... },\n    ...\n  ],\n  \"metadata\": {\n    \"clock-domain\": \"LINUX_CLOCK_MONOTONIC\"\n  }\n}\n```\n\nThe `metadata.clock-domain` field is set based on the host platform (Linux or macOS). On Windows it is omitted.\n\n## Viewing Profiles\n\nLoad the output JSON file in any of these tools:\n\n- **Chrome DevTools**: Performance panel > Load profile (Ctrl+Shift+I > Performance)\n- **Perfetto UI**: https://ui.perfetto.dev/ -- drag and drop the JSON file\n- **Trace Viewer**: `chrome://tracing` in any Chromium browser\n\n## Limitations\n\n- Only works with Chromium-based browsers (Chrome, Edge). Not supported on Firefox or WebKit.\n- Trace data accumulates in memory while profiling is active (capped at 5 million events). Stop profiling promptly after the area of interest.\n- Data collection on stop has a 30-second timeout. If the browser is unresponsive, the stop command may fail.\n",".agents/skills/agent-browser/references/proxy-support.md":"# Proxy Support\n\nProxy configuration for geo-testing, rate limiting avoidance, and corporate environments.\n\n**Related**: [commands.md](commands.md) for global options, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [Basic Proxy Configuration](#basic-proxy-configuration)\n- [Authenticated Proxy](#authenticated-proxy)\n- [SOCKS Proxy](#socks-proxy)\n- [Proxy Bypass](#proxy-bypass)\n- [Common Use Cases](#common-use-cases)\n- [Verifying Proxy Connection](#verifying-proxy-connection)\n- [Troubleshooting](#troubleshooting)\n- [Best Practices](#best-practices)\n\n## Basic Proxy Configuration\n\nUse the `--proxy` flag or set proxy via environment variable:\n\n```bash\n# Via CLI flag\nagent-browser --proxy \"http://proxy.example.com:8080\" open https://example.com\n\n# Via environment variable\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 using `--proxy-bypass` or `NO_PROXY`:\n\n```bash\n# Via CLI flag\nagent-browser --proxy \"http://proxy.example.com:8080\" --proxy-bypass \"localhost,*.internal.com\" open https://example.com\n\n# Via environment variable\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\nMultiple isolated browser sessions with state persistence and concurrent browsing.\n\n**Related**: [authentication.md](authentication.md) for login patterns, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [Named Sessions](#named-sessions)\n- [Session Isolation Properties](#session-isolation-properties)\n- [Session State Persistence](#session-state-persistence)\n- [Common Patterns](#common-patterns)\n- [Default Session](#default-session)\n- [Session Cleanup](#session-cleanup)\n- [Best Practices](#best-practices)\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\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 and Refs\n\nCompact element references that reduce context usage dramatically for AI agents.\n\n**Related**: [commands.md](commands.md) for full command reference, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [How Refs Work](#how-refs-work)\n- [Snapshot Command](#the-snapshot-command)\n- [Using Refs](#using-refs)\n- [Ref Lifecycle](#ref-lifecycle)\n- [Best Practices](#best-practices)\n- [Ref Notation Details](#ref-notation-details)\n- [Troubleshooting](#troubleshooting)\n\n## How Refs Work\n\nTraditional approach:\n\n```\nFull DOM/HTML → AI parses → CSS selector → Action (~3000-5000 tokens)\n```\n\nagent-browser approach:\n\n```\nCompact snapshot → @refs assigned → Direct interaction (~200-400 tokens)\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 down to reveal element\nagent-browser scroll down 1000\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":"# Video Recording\n\nCapture browser automation as video for debugging, documentation, or verification.\n\n**Related**: [commands.md](commands.md) for full command reference, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [Basic Recording](#basic-recording)\n- [Recording Commands](#recording-commands)\n- [Use Cases](#use-cases)\n- [Best Practices](#best-practices)\n- [Output Format](#output-format)\n- [Limitations](#limitations)\n\n## Basic Recording\n\n```bash\n# Start recording\nagent-browser record start ./demo.webm\n\n# Perform actions\nagent-browser open https://example.com\nagent-browser snapshot -i\nagent-browser click @e1\nagent-browser fill @e2 \"test input\"\n\n# Stop and save\nagent-browser record stop\n```\n\n## Recording Commands\n\n```bash\n# Start recording to file\nagent-browser record start ./output.webm\n\n# Stop current recording\nagent-browser record stop\n\n# Restart with new file (stops current + starts new)\nagent-browser record restart ./take2.webm\n```\n\n## Use Cases\n\n### Debugging Failed Automation\n\n```bash\n#!/bin/bash\n# Record automation for debugging\n\nagent-browser record start ./debug-$(date +%Y%m%d-%H%M%S).webm\n\n# Run your automation\nagent-browser open https://app.example.com\nagent-browser snapshot -i\nagent-browser click @e1 || {\n    echo \"Click failed - check recording\"\n    agent-browser record stop\n    exit 1\n}\n\nagent-browser record stop\n```\n\n### Documentation Generation\n\n```bash\n#!/bin/bash\n# Record workflow for documentation\n\nagent-browser record start ./docs/how-to-login.webm\n\nagent-browser open https://app.example.com/login\nagent-browser wait 1000  # Pause for visibility\n\nagent-browser snapshot -i\nagent-browser fill @e1 \"demo@example.com\"\nagent-browser wait 500\n\nagent-browser fill @e2 \"password\"\nagent-browser wait 500\n\nagent-browser click @e3\nagent-browser wait --load networkidle\nagent-browser wait 1000  # Show result\n\nagent-browser record stop\n```\n\n### CI/CD Test Evidence\n\n```bash\n#!/bin/bash\n# Record E2E test runs for CI artifacts\n\nTEST_NAME=\"${1:-e2e-test}\"\nRECORDING_DIR=\"./test-recordings\"\nmkdir -p \"$RECORDING_DIR\"\n\nagent-browser record start \"$RECORDING_DIR/$TEST_NAME-$(date +%s).webm\"\n\n# Run test\nif run_e2e_test; then\n    echo \"Test passed\"\nelse\n    echo \"Test failed - recording saved\"\nfi\n\nagent-browser record stop\n```\n\n## Best Practices\n\n### 1. Add Pauses for Clarity\n\n```bash\n# Slow down for human viewing\nagent-browser click @e1\nagent-browser wait 500  # Let viewer see result\n```\n\n### 2. Use Descriptive Filenames\n\n```bash\n# Include context in filename\nagent-browser record start ./recordings/login-flow-2024-01-15.webm\nagent-browser record start ./recordings/checkout-test-run-42.webm\n```\n\n### 3. Handle Recording in Error Cases\n\n```bash\n#!/bin/bash\nset -e\n\ncleanup() {\n    agent-browser record stop 2>/dev/null || true\n    agent-browser close 2>/dev/null || true\n}\ntrap cleanup EXIT\n\nagent-browser record start ./automation.webm\n# ... automation steps ...\n```\n\n### 4. Combine with Screenshots\n\n```bash\n# Record video AND capture key frames\nagent-browser record start ./flow.webm\n\nagent-browser open https://example.com\nagent-browser screenshot ./screenshots/step1-homepage.png\n\nagent-browser click @e1\nagent-browser screenshot ./screenshots/step2-after-click.png\n\nagent-browser record stop\n```\n\n## Output Format\n\n- Default format: WebM (VP8/VP9 codec)\n- Compatible with all modern browsers and video players\n- Compressed but high quality\n\n## Limitations\n\n- Recording adds slight overhead to automation\n- Large recordings can consume significant disk space\n- Some headless environments may have codec limitations\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 <pre key={`${message.id}-${i}`}>{JSON.stringify(part.toolInvocation, null, 2)}</pre>\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 (part.type === 'tool-getWeather' && (part.state === 'input-available' || part.state === 'output-available')) {\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 (part.type === 'tool-getWeather' && (part.state === 'input-available' || part.state === 'output-available')) {\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({ invocation }: { invocation: WeatherToolInvocation }) {\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/bibi/SKILL.md":"---\nname: bibi\ndescription: >\n  BibiGPT CLI for summarizing videos, audio, and podcasts directly in the terminal.\n  Use when the user wants to summarize a URL (YouTube, Bilibili, podcast, etc.)\n  or check their BibiGPT authentication status.\n  Requires the BibiGPT desktop app installed with an active login session,\n  or a BIBI_API_TOKEN environment variable set.\n---\n\n# BibiGPT CLI (`bibi`)\n\nSummarize videos, audio, and podcasts from the terminal using the BibiGPT API.\n\n## Installation\n\n### macOS (Homebrew)\n\n```bash\nbrew install --cask jimmylv/bibigpt/bibigpt\n```\n\n### Windows\n\nDownload the installer from: **https://bibigpt.co/download/desktop**\n\n> `winget install BibiGPT` is pending review and not yet available.\n\n### Verify installation\n\n```bash\nbibi --version\n```\n\n## Authentication\n\nAfter installing, the user must log in via the desktop app at least once. The CLI reads the saved session automatically from the desktop app's settings.\n\nAlternatively, set an API token:\n\n```bash\nexport BIBI_API_TOKEN=<token>\n```\n\nOn Windows (PowerShell):\n\n```powershell\n$env:BIBI_API_TOKEN=\"<token>\"\n```\n\n## Commands\n\n### Summarize a URL\n\n**Important**: URLs containing `?` or `&` must be quoted to avoid shell glob errors.\n\n```bash\n# Basic summary (Markdown output to stdout)\nbibi summarize \"<URL>\"\n\n# Async mode — recommended for long videos (>30min)\nbibi summarize \"<URL>\" --async\n\n# Chapter-by-chapter summary\nbibi summarize \"<URL>\" --chapter\n\n# Fetch subtitles/transcript only (no AI summary)\nbibi summarize \"<URL>\" --subtitle\n\n# Full JSON response\nbibi summarize \"<URL>\" --json\n\n# Combine flags\nbibi summarize \"<URL>\" --subtitle --json\n```\n\n**Supported URL types**: YouTube, Bilibili, podcasts, audio files, and any URL supported by BibiGPT.\n\n### Authentication\n\n```bash\n# Check current auth status\nbibi auth check\n\n# Open browser to log in\nbibi auth login\n\n# Show how to set API token\nbibi auth set-token <TOKEN>\n```\n\n### Updates\n\n```bash\n# Check if a new version is available\nbibi check-update\n\n# Download and install the latest version\nbibi self-update\n```\n\n## Output Format\n\n- **Default**: Markdown summary text sent to stdout. Progress info goes to stderr.\n- **--json**: Complete API response as pretty-printed JSON to stdout.\n\nThis means you can pipe the output:\n\n```bash\nbibi summarize \"<URL>\" > summary.md\nbibi summarize \"<URL>\" --json | jq '.summary'\n```\n\n## Error Handling\n\n| Exit Code | Meaning                                        |\n| --------- | ---------------------------------------------- |\n| 0         | Success                                        |\n| 1         | Error (auth missing, API error, timeout, etc.) |\n\n| HTTP Status | User Action                                                      |\n| ----------- | ---------------------------------------------------------------- |\n| 401         | Token expired — run `bibi auth login` or re-login in desktop app |\n| 402/403     | Quota exceeded — visit https://bibigpt.co/pricing                |\n| 429         | Rate limited — wait and retry                                    |\n\n## Usage Tips\n\n- For very long videos, use `--async` to avoid HTTP timeout.\n- Use `--subtitle` to get raw subtitles/transcript without AI summarization.\n- Use `--json` when you need structured data (e.g., `sourceUrl`, `htmlUrl`, `detail`).\n- The `--chapter` flag provides section-by-section summaries, useful for lectures or tutorials.\n- The CLI does NOT open any GUI window — all output goes to the terminal.\n- Run `bibi check-update` periodically to get new features and bug fixes.\n",".agents/skills/find-skills/SKILL.md":"---\nname: find-skills\ndescription: Helps users discover and install agent skills when they ask questions like \"how do I do X\", \"find a skill for X\", \"is there a skill that can...\", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.\n---\n\n# Find Skills\n\nThis skill helps you discover and install skills from the open agent skills ecosystem.\n\n## When to Use This Skill\n\nUse this skill when the user:\n\n- Asks \"how do I do X\" where X might be a common task with an existing skill\n- Says \"find a skill for X\" or \"is there a skill for X\"\n- Asks \"can you do X\" where X is a specialized capability\n- Expresses interest in extending agent capabilities\n- Wants to search for tools, templates, or workflows\n- Mentions they wish they had help with a specific domain (design, testing, deployment, etc.)\n\n## What is the Skills CLI?\n\nThe Skills CLI (`npx skills`) is the package manager for the open agent skills ecosystem. Skills are modular packages that extend agent capabilities with specialized knowledge, workflows, and tools.\n\n**Key commands:**\n\n- `npx skills find [query]` - Search for skills interactively or by keyword\n- `npx skills add <package>` - Install a skill from GitHub or other sources\n- `npx skills check` - Check for skill updates\n- `npx skills update` - Update all installed skills\n\n**Browse skills at:** https://skills.sh/\n\n## How to Help Users Find Skills\n\n### Step 1: Understand What They Need\n\nWhen a user asks for help with something, identify:\n\n1. The domain (e.g., React, testing, design, deployment)\n2. The specific task (e.g., writing tests, creating animations, reviewing PRs)\n3. Whether this is a common enough task that a skill likely exists\n\n### Step 2: Search for Skills\n\nRun the find command with a relevant query:\n\n```bash\nnpx skills find [query]\n```\n\nFor example:\n\n- User asks \"how do I make my React app faster?\" → `npx skills find react performance`\n- User asks \"can you help me with PR reviews?\" → `npx skills find pr review`\n- User asks \"I need to create a changelog\" → `npx skills find changelog`\n\nThe command will return results like:\n\n```\nInstall with npx skills add <owner/repo@skill>\n\nvercel-labs/agent-skills@vercel-react-best-practices\n└ https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices\n```\n\n### Step 3: Present Options to the User\n\nWhen you find relevant skills, present them to the user with:\n\n1. The skill name and what it does\n2. The install command they can run\n3. A link to learn more at skills.sh\n\nExample response:\n\n```\nI found a skill that might help! The \"vercel-react-best-practices\" skill provides\nReact and Next.js performance optimization guidelines from Vercel Engineering.\n\nTo install it:\nnpx skills add vercel-labs/agent-skills@vercel-react-best-practices\n\nLearn more: https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices\n```\n\n### Step 4: Offer to Install\n\nIf the user wants to proceed, you can install the skill for them:\n\n```bash\nnpx skills add <owner/repo@skill> -g -y\n```\n\nThe `-g` flag installs globally (user-level) and `-y` skips confirmation prompts.\n\n## Common Skill Categories\n\nWhen searching, consider these common categories:\n\n| Category        | Example Queries                          |\n| --------------- | ---------------------------------------- |\n| Web Development | react, nextjs, typescript, css, tailwind |\n| Testing         | testing, jest, playwright, e2e           |\n| DevOps          | deploy, docker, kubernetes, ci-cd        |\n| Documentation   | docs, readme, changelog, api-docs        |\n| Code Quality    | review, lint, refactor, best-practices   |\n| Design          | ui, ux, design-system, accessibility     |\n| Productivity    | workflow, automation, git                |\n\n## Tips for Effective Searches\n\n1. **Use specific keywords**: \"react testing\" is better than just \"testing\"\n2. **Try alternative terms**: If \"deploy\" doesn't work, try \"deployment\" or \"ci-cd\"\n3. **Check popular sources**: Many skills come from `vercel-labs/agent-skills` or `ComposioHQ/awesome-claude-skills`\n\n## When No Skills Are Found\n\nIf no relevant skills exist:\n\n1. Acknowledge that no existing skill was found\n2. Offer to help with the task directly using your general capabilities\n3. Suggest the user could create their own skill with `npx skills init`\n\nExample:\n\n```\nI searched for skills related to \"xyz\" but didn't find any matches.\nI can still help you with this task directly! Would you like me to proceed?\n\nIf this is something you do often, you could create your own skill:\nnpx skills init my-xyz-skill\n```\n",".agents/skills/next-best-practices/SKILL.md":"---\nname: next-best-practices\ndescription: Next.js best practices - file conventions, RSC boundaries, data patterns, async APIs, metadata, error handling, route handlers, image/font optimization, bundling\nuser-invocable: false\n---\n\n# Next.js Best Practices\n\nApply these rules when writing or reviewing Next.js code.\n\n## File Conventions\n\nSee [file-conventions.md](./file-conventions.md) for:\n\n- Project structure and special files\n- Route segments (dynamic, catch-all, groups)\n- Parallel and intercepting routes\n- Middleware rename in v16 (middleware → proxy)\n\n## RSC Boundaries\n\nDetect invalid React Server Component patterns.\n\nSee [rsc-boundaries.md](./rsc-boundaries.md) for:\n\n- Async client component detection (invalid)\n- Non-serializable props detection\n- Server Action exceptions\n\n## Async Patterns\n\nNext.js 15+ async API changes.\n\nSee [async-patterns.md](./async-patterns.md) for:\n\n- Async `params` and `searchParams`\n- Async `cookies()` and `headers()`\n- Migration codemod\n\n## Runtime Selection\n\nSee [runtime-selection.md](./runtime-selection.md) for:\n\n- Default to Node.js runtime\n- When Edge runtime is appropriate\n\n## Directives\n\nSee [directives.md](./directives.md) for:\n\n- `'use client'`, `'use server'` (React)\n- `'use cache'` (Next.js)\n\n## Functions\n\nSee [functions.md](./functions.md) for:\n\n- Navigation hooks: `useRouter`, `usePathname`, `useSearchParams`, `useParams`\n- Server functions: `cookies`, `headers`, `draftMode`, `after`\n- Generate functions: `generateStaticParams`, `generateMetadata`\n\n## Error Handling\n\nSee [error-handling.md](./error-handling.md) for:\n\n- `error.tsx`, `global-error.tsx`, `not-found.tsx`\n- `redirect`, `permanentRedirect`, `notFound`\n- `forbidden`, `unauthorized` (auth errors)\n- `unstable_rethrow` for catch blocks\n\n## Data Patterns\n\nSee [data-patterns.md](./data-patterns.md) for:\n\n- Server Components vs Server Actions vs Route Handlers\n- Avoiding data waterfalls (`Promise.all`, Suspense, preload)\n- Client component data fetching\n\n## Route Handlers\n\nSee [route-handlers.md](./route-handlers.md) for:\n\n- `route.ts` basics\n- GET handler conflicts with `page.tsx`\n- Environment behavior (no React DOM)\n- When to use vs Server Actions\n\n## Metadata & OG Images\n\nSee [metadata.md](./metadata.md) for:\n\n- Static and dynamic metadata\n- `generateMetadata` function\n- OG image generation with `next/og`\n- File-based metadata conventions\n\n## Image Optimization\n\nSee [image.md](./image.md) for:\n\n- Always use `next/image` over `<img>`\n- Remote images configuration\n- Responsive `sizes` attribute\n- Blur placeholders\n- Priority loading for LCP\n\n## Font Optimization\n\nSee [font.md](./font.md) for:\n\n- `next/font` setup\n- Google Fonts, local fonts\n- Tailwind CSS integration\n- Preloading subsets\n\n## Bundling\n\nSee [bundling.md](./bundling.md) for:\n\n- Server-incompatible packages\n- CSS imports (not link tags)\n- Polyfills (already included)\n- ESM/CommonJS issues\n- Bundle analysis\n\n## Scripts\n\nSee [scripts.md](./scripts.md) for:\n\n- `next/script` vs native script tags\n- Inline scripts need `id`\n- Loading strategies\n- Google Analytics with `@next/third-parties`\n\n## Hydration Errors\n\nSee [hydration-error.md](./hydration-error.md) for:\n\n- Common causes (browser APIs, dates, invalid HTML)\n- Debugging with error overlay\n- Fixes for each cause\n\n## Suspense Boundaries\n\nSee [suspense-boundaries.md](./suspense-boundaries.md) for:\n\n- CSR bailout with `useSearchParams` and `usePathname`\n- Which hooks require Suspense boundaries\n\n## Parallel & Intercepting Routes\n\nSee [parallel-routes.md](./parallel-routes.md) for:\n\n- Modal patterns with `@slot` and `(.)` interceptors\n- `default.tsx` for fallbacks\n- Closing modals correctly with `router.back()`\n\n## Self-Hosting\n\nSee [self-hosting.md](./self-hosting.md) for:\n\n- `output: 'standalone'` for Docker\n- Cache handlers for multi-instance ISR\n- What works vs needs extra setup\n\n## Debug Tricks\n\nSee [debug-tricks.md](./debug-tricks.md) for:\n\n- MCP endpoint for AI-assisted debugging\n- Rebuild specific routes with `--debug-build-paths`\n",".agents/skills/next-best-practices/async-patterns.md":"# Async Patterns\n\nIn Next.js 15+, `params`, `searchParams`, `cookies()`, and `headers()` are asynchronous.\n\n## Async Params and SearchParams\n\nAlways type them as `Promise<...>` and await them.\n\n### Pages and Layouts\n\n```tsx\ntype Props = { params: Promise<{ slug: string }> }\n\nexport default async function Page({ params }: Props) {\n  const { slug } = await params\n}\n```\n\n### Route Handlers\n\n```tsx\nexport async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n}\n```\n\n### SearchParams\n\n```tsx\ntype Props = {\n  params: Promise<{ slug: string }>\n  searchParams: Promise<{ query?: string }>\n}\n\nexport default async function Page({ params, searchParams }: Props) {\n  const { slug } = await params\n  const { query } = await searchParams\n}\n```\n\n### Synchronous Components\n\nUse `React.use()` for non-async components:\n\n```tsx\nimport { use } from 'react'\n\ntype Props = { params: Promise<{ slug: string }> }\n\nexport default function Page({ params }: Props) {\n  const { slug } = use(params)\n}\n```\n\n### generateMetadata\n\n```tsx\ntype Props = { params: Promise<{ slug: string }> }\n\nexport async function generateMetadata({ params }: Props): Promise<Metadata> {\n  const { slug } = await params\n  return { title: slug }\n}\n```\n\n## Async Cookies and Headers\n\n```tsx\nimport { cookies, headers } from 'next/headers'\n\nexport default async function Page() {\n  const cookieStore = await cookies()\n  const headersList = await headers()\n\n  const theme = cookieStore.get('theme')\n  const userAgent = headersList.get('user-agent')\n}\n```\n\n## Migration Codemod\n\n```bash\nnpx @next/codemod@latest next-async-request-api .\n```\n",".agents/skills/next-best-practices/bundling.md":"# Bundling\n\nFix common bundling issues with third-party packages.\n\n## Server-Incompatible Packages\n\nSome packages use browser APIs (`window`, `document`, `localStorage`) and fail in Server Components.\n\n### Error Signs\n\n```\nReferenceError: window is not defined\nReferenceError: document is not defined\nReferenceError: localStorage is not defined\nModule not found: Can't resolve 'fs'\n```\n\n### Solution 1: Mark as Client-Only\n\nIf the package is only needed on client:\n\n```tsx\n// Bad: Fails - package uses window\nimport SomeChart from 'some-chart-library'\n\nexport default function Page() {\n  return <SomeChart />\n}\n\n// Good: Use dynamic import with ssr: false\nimport dynamic from 'next/dynamic'\n\nconst SomeChart = dynamic(() => import('some-chart-library'), {\n  ssr: false,\n})\n\nexport default function Page() {\n  return <SomeChart />\n}\n```\n\n### Solution 2: Externalize from Server Bundle\n\nFor packages that should run on server but have bundling issues:\n\n```js\n// next.config.js\nmodule.exports = {\n  serverExternalPackages: ['problematic-package'],\n}\n```\n\nUse this for:\n\n- Packages with native bindings (sharp, bcrypt)\n- Packages that don't bundle well (some ORMs)\n- Packages with circular dependencies\n\n### Solution 3: Client Component Wrapper\n\nWrap the entire usage in a client component:\n\n```tsx\n// components/ChartWrapper.tsx\n'use client'\n\nimport { Chart } from 'chart-library'\n\nexport function ChartWrapper(props) {\n  return <Chart {...props} />\n}\n\n// app/page.tsx (server component)\nimport { ChartWrapper } from '@/components/ChartWrapper'\n\nexport default function Page() {\n  return <ChartWrapper data={data} />\n}\n```\n\n## CSS Imports\n\nImport CSS files instead of using `<link>` tags. Next.js handles bundling and optimization.\n\n```tsx\n// Bad: Manual link tag\n;<link rel=\"stylesheet\" href=\"/styles.css\" />\n\n// Good: Import CSS\nimport './styles.css'\n\n// Good: CSS Modules\nimport styles from './Button.module.css'\n```\n\n## Polyfills\n\nNext.js includes common polyfills automatically. Don't load redundant ones from polyfill.io or similar CDNs.\n\nAlready included: `Array.from`, `Object.assign`, `Promise`, `fetch`, `Map`, `Set`, `Symbol`, `URLSearchParams`, and 50+ others.\n\n```tsx\n// Bad: Redundant polyfills\n<script src=\"https://polyfill.io/v3/polyfill.min.js?features=fetch,Promise,Array.from\" />\n\n// Good: Next.js includes these automatically\n```\n\n## ESM/CommonJS Issues\n\n### Error Signs\n\n```\nSyntaxError: Cannot use import statement outside a module\nError: require() of ES Module\nModule not found: ESM packages need to be imported\n```\n\n### Solution: Transpile Package\n\n```js\n// next.config.js\nmodule.exports = {\n  transpilePackages: ['some-esm-package', 'another-package'],\n}\n```\n\n## Common Problematic Packages\n\n| Package         | Issue           | Solution                                                        |\n| --------------- | --------------- | --------------------------------------------------------------- |\n| `sharp`         | Native bindings | `serverExternalPackages: ['sharp']`                             |\n| `bcrypt`        | Native bindings | `serverExternalPackages: ['bcrypt']` or use `bcryptjs`          |\n| `canvas`        | Native bindings | `serverExternalPackages: ['canvas']`                            |\n| `recharts`      | Uses window     | `dynamic(() => import('recharts'), { ssr: false })`             |\n| `react-quill`   | Uses document   | `dynamic(() => import('react-quill'), { ssr: false })`          |\n| `mapbox-gl`     | Uses window     | `dynamic(() => import('mapbox-gl'), { ssr: false })`            |\n| `monaco-editor` | Uses window     | `dynamic(() => import('@monaco-editor/react'), { ssr: false })` |\n| `lottie-web`    | Uses document   | `dynamic(() => import('lottie-react'), { ssr: false })`         |\n\n## Bundle Analysis\n\nAnalyze bundle size with the built-in analyzer (Next.js 16.1+):\n\n```bash\nnext experimental-analyze\n```\n\nThis opens an interactive UI to:\n\n- Filter by route, environment (client/server), and type\n- Inspect module sizes and import chains\n- View treemap visualization\n\nSave output for comparison:\n\n```bash\nnext experimental-analyze --output\n# Output saved to .next/diagnostics/analyze\n```\n\nReference: https://nextjs.org/docs/app/guides/package-bundling\n\n## Migrating from Webpack to Turbopack\n\nTurbopack is the default bundler in Next.js 15+. If you have custom webpack config, migrate to Turbopack-compatible alternatives:\n\n```js\n// next.config.js\nmodule.exports = {\n  // Good: Works with Turbopack\n  serverExternalPackages: ['package'],\n  transpilePackages: ['package'],\n\n  // Bad: Webpack-only - migrate away from this\n  webpack: (config) => {\n    // custom webpack config\n  },\n}\n```\n\nReference: https://nextjs.org/docs/app/building-your-application/upgrading/from-webpack-to-turbopack\n",".agents/skills/next-best-practices/data-patterns.md":"# Data Patterns\n\nChoose the right data fetching pattern for each use case.\n\n## Decision Tree\n\n```\nNeed to fetch data?\n├── From a Server Component?\n│   └── Use: Fetch directly (no API needed)\n│\n├── From a Client Component?\n│   ├── Is it a mutation (POST/PUT/DELETE)?\n│   │   └── Use: Server Action\n│   └── Is it a read (GET)?\n│       └── Use: Route Handler OR pass from Server Component\n│\n├── Need external API access (webhooks, third parties)?\n│   └── Use: Route Handler\n│\n└── Need REST API for mobile app / external clients?\n    └── Use: Route Handler\n```\n\n## Pattern 1: Server Components (Preferred for Reads)\n\nFetch data directly in Server Components - no API layer needed.\n\n```tsx\n// app/users/page.tsx\nasync function UsersPage() {\n  // Direct database access - no API round-trip\n  const users = await db.user.findMany()\n\n  // Or fetch from external API\n  const posts = await fetch('https://api.example.com/posts').then((r) => r.json())\n\n  return (\n    <ul>\n      {users.map((user) => (\n        <li key={user.id}>{user.name}</li>\n      ))}\n    </ul>\n  )\n}\n```\n\n**Benefits**:\n\n- No API to maintain\n- No client-server waterfall\n- Secrets stay on server\n- Direct database access\n\n## Pattern 2: Server Actions (Preferred for Mutations)\n\nServer Actions are the recommended way to handle mutations.\n\n```tsx\n// app/actions.ts\n'use server'\n\nimport { revalidatePath } from 'next/cache'\n\nexport async function createPost(formData: FormData) {\n  const title = formData.get('title') as string\n\n  await db.post.create({ data: { title } })\n\n  revalidatePath('/posts')\n}\n\nexport async function deletePost(id: string) {\n  await db.post.delete({ where: { id } })\n\n  revalidateTag('posts')\n}\n```\n\n```tsx\n// app/posts/new/page.tsx\nimport { createPost } from '@/app/actions'\n\nexport default function NewPost() {\n  return (\n    <form action={createPost}>\n      <input name=\"title\" required />\n      <button type=\"submit\">Create</button>\n    </form>\n  )\n}\n```\n\n**Benefits**:\n\n- End-to-end type safety\n- Progressive enhancement (works without JS)\n- Automatic request handling\n- Integrated with React transitions\n\n**Constraints**:\n\n- POST only (no GET caching semantics)\n- Internal use only (no external access)\n- Cannot return non-serializable data\n\n## Pattern 3: Route Handlers (APIs)\n\nUse Route Handlers when you need a REST API.\n\n```tsx\n// app/api/posts/route.ts\nimport { NextRequest, NextResponse } from 'next/server'\n\n// GET is cacheable\nexport async function GET(request: NextRequest) {\n  const posts = await db.post.findMany()\n  return NextResponse.json(posts)\n}\n\n// POST for mutations\nexport async function POST(request: NextRequest) {\n  const body = await request.json()\n  const post = await db.post.create({ data: body })\n  return NextResponse.json(post, { status: 201 })\n}\n```\n\n**When to use**:\n\n- External API access (mobile apps, third parties)\n- Webhooks from external services\n- GET endpoints that need HTTP caching\n- OpenAPI/Swagger documentation needed\n\n**When NOT to use**:\n\n- Internal data fetching (use Server Components)\n- Mutations from your UI (use Server Actions)\n\n## Avoiding Data Waterfalls\n\n### Problem: Sequential Fetches\n\n```tsx\n// Bad: Sequential waterfalls\nasync function Dashboard() {\n  const user = await getUser() // Wait...\n  const posts = await getPosts() // Then wait...\n  const comments = await getComments() // Then wait...\n\n  return <div>...</div>\n}\n```\n\n### Solution 1: Parallel Fetching with Promise.all\n\n```tsx\n// Good: Parallel fetching\nasync function Dashboard() {\n  const [user, posts, comments] = await Promise.all([getUser(), getPosts(), getComments()])\n\n  return <div>...</div>\n}\n```\n\n### Solution 2: Streaming with Suspense\n\n```tsx\n// Good: Show content progressively\nimport { Suspense } from 'react'\n\nasync function Dashboard() {\n  return (\n    <div>\n      <Suspense fallback={<UserSkeleton />}>\n        <UserSection />\n      </Suspense>\n      <Suspense fallback={<PostsSkeleton />}>\n        <PostsSection />\n      </Suspense>\n    </div>\n  )\n}\n\nasync function UserSection() {\n  const user = await getUser() // Fetches independently\n  return <div>{user.name}</div>\n}\n\nasync function PostsSection() {\n  const posts = await getPosts() // Fetches independently\n  return <PostList posts={posts} />\n}\n```\n\n### Solution 3: Preload Pattern\n\n```tsx\n// lib/data.ts\nimport { cache } from 'react'\n\nexport const getUser = cache(async (id: string) => {\n  return db.user.findUnique({ where: { id } })\n})\n\nexport const preloadUser = (id: string) => {\n  void getUser(id) // Fire and forget\n}\n```\n\n```tsx\n// app/user/[id]/page.tsx\nimport { getUser, preloadUser } from '@/lib/data'\n\nexport default async function UserPage({ params }) {\n  const { id } = await params\n\n  // Start fetching early\n  preloadUser(id)\n\n  // Do other work...\n\n  // Data likely ready by now\n  const user = await getUser(id)\n  return <div>{user.name}</div>\n}\n```\n\n## Client Component Data Fetching\n\nWhen Client Components need data:\n\n### Option 1: Pass from Server Component (Preferred)\n\n```tsx\n// Server Component\nasync function Page() {\n  const data = await fetchData()\n  return <ClientComponent initialData={data} />\n}\n\n// Client Component\n;('use client')\nfunction ClientComponent({ initialData }) {\n  const [data, setData] = useState(initialData)\n  // ...\n}\n```\n\n### Option 2: Fetch on Mount (When Necessary)\n\n```tsx\n'use client'\nimport { useEffect, useState } from 'react'\n\nfunction ClientComponent() {\n  const [data, setData] = useState(null)\n\n  useEffect(() => {\n    fetch('/api/data')\n      .then((r) => r.json())\n      .then(setData)\n  }, [])\n\n  if (!data) return <Loading />\n  return <div>{data.value}</div>\n}\n```\n\n### Option 3: Server Action for Reads (Works But Not Ideal)\n\nServer Actions can be called from Client Components for reads, but this is not their intended purpose:\n\n```tsx\n'use client'\nimport { getData } from './actions'\nimport { useEffect, useState } from 'react'\n\nfunction ClientComponent() {\n  const [data, setData] = useState(null)\n\n  useEffect(() => {\n    getData().then(setData)\n  }, [])\n\n  return <div>{data?.value}</div>\n}\n```\n\n**Note**: Server Actions always use POST, so no HTTP caching. Prefer Route Handlers for cacheable reads.\n\n## Quick Reference\n\n| Pattern                | Use Case                    | HTTP Method | Caching              |\n| ---------------------- | --------------------------- | ----------- | -------------------- |\n| Server Component fetch | Internal reads              | Any         | Full Next.js caching |\n| Server Action          | Mutations, form submissions | POST only   | No                   |\n| Route Handler          | External APIs, webhooks     | Any         | GET can be cached    |\n| Client fetch to API    | Client-side reads           | Any         | HTTP cache headers   |\n",".agents/skills/next-best-practices/debug-tricks.md":"# Debug Tricks\n\nTricks to speed up debugging Next.js applications.\n\n## MCP Endpoint (Dev Server)\n\nNext.js exposes a `/_next/mcp` endpoint in development for AI-assisted debugging via MCP (Model Context Protocol).\n\n- **Next.js 16+**: Enabled by default, use `next-devtools-mcp`\n- **Next.js < 16**: Requires `experimental.mcpServer: true` in next.config.js\n\nReference: https://nextjs.org/docs/app/guides/mcp\n\n**Important**: Find the actual port of the running Next.js dev server (check terminal output or `package.json` scripts). Don't assume port 3000.\n\n### Request Format\n\nThe endpoint uses JSON-RPC 2.0 over HTTP POST:\n\n```bash\ncurl -X POST http://localhost:<port>/_next/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json, text/event-stream\" \\\n  -d '{\n    \"jsonrpc\": \"2.0\",\n    \"id\": \"1\",\n    \"method\": \"tools/call\",\n    \"params\": {\n      \"name\": \"<tool-name>\",\n      \"arguments\": {}\n    }\n  }'\n```\n\n### Available Tools\n\n#### `get_errors`\n\nGet current errors from dev server (build errors, runtime errors with source-mapped stacks):\n\n```json\n{ \"name\": \"get_errors\", \"arguments\": {} }\n```\n\n#### `get_routes`\n\nDiscover all routes by scanning filesystem:\n\n```json\n{ \"name\": \"get_routes\", \"arguments\": {} }\n// Optional: { \"name\": \"get_routes\", \"arguments\": { \"routerType\": \"app\" } }\n```\n\nReturns: `{ \"appRouter\": [\"/\", \"/api/users/[id]\", ...], \"pagesRouter\": [...] }`\n\n#### `get_project_metadata`\n\nGet project path and dev server URL:\n\n```json\n{ \"name\": \"get_project_metadata\", \"arguments\": {} }\n```\n\nReturns: `{ \"projectPath\": \"/path/to/project\", \"devServerUrl\": \"http://localhost:3000\" }`\n\n#### `get_page_metadata`\n\nGet runtime metadata about current page render (requires active browser session):\n\n```json\n{ \"name\": \"get_page_metadata\", \"arguments\": {} }\n```\n\nReturns segment trie data showing layouts, boundaries, and page components.\n\n#### `get_logs`\n\nGet path to Next.js development log file:\n\n```json\n{ \"name\": \"get_logs\", \"arguments\": {} }\n```\n\nReturns path to `<distDir>/logs/next-development.log`\n\n#### `get_server_action_by_id`\n\nLocate a Server Action by ID:\n\n```json\n{ \"name\": \"get_server_action_by_id\", \"arguments\": { \"actionId\": \"<action-id>\" } }\n```\n\n### Example: Get Errors\n\n```bash\ncurl -X POST http://localhost:<port>/_next/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json, text/event-stream\" \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"tools/call\",\"params\":{\"name\":\"get_errors\",\"arguments\":{}}}'\n```\n\n## Rebuild Specific Routes (Next.js 16+)\n\nUse `--debug-build-paths` to rebuild only specific routes instead of the entire app:\n\n```bash\n# Rebuild a specific route\nnext build --debug-build-paths \"/dashboard\"\n\n# Rebuild routes matching a glob\nnext build --debug-build-paths \"/api/*\"\n\n# Dynamic routes\nnext build --debug-build-paths \"/blog/[slug]\"\n```\n\nUse this to:\n\n- Quickly verify a build fix without full rebuild\n- Debug static generation issues for specific pages\n- Iterate faster on build errors\n",".agents/skills/next-best-practices/directives.md":"# Directives\n\n## React Directives\n\nThese are React directives, not Next.js specific.\n\n### `'use client'`\n\nMarks a component as a Client Component. Required for:\n\n- React hooks (`useState`, `useEffect`, etc.)\n- Event handlers (`onClick`, `onChange`)\n- Browser APIs (`window`, `localStorage`)\n\n```tsx\n'use client'\n\nimport { useState } from 'react'\n\nexport function Counter() {\n  const [count, setCount] = useState(0)\n  return <button onClick={() => setCount(count + 1)}>{count}</button>\n}\n```\n\nReference: https://react.dev/reference/rsc/use-client\n\n### `'use server'`\n\nMarks a function as a Server Action. Can be passed to Client Components.\n\n```tsx\n'use server'\n\nexport async function submitForm(formData: FormData) {\n  // Runs on server\n}\n```\n\nOr inline within a Server Component:\n\n```tsx\nexport default function Page() {\n  async function submit() {\n    'use server'\n    // Runs on server\n  }\n  return <form action={submit}>...</form>\n}\n```\n\nReference: https://react.dev/reference/rsc/use-server\n\n---\n\n## Next.js Directive\n\n### `'use cache'`\n\nMarks a function or component for caching. Part of Next.js Cache Components.\n\n```tsx\n'use cache'\n\nexport async function getCachedData() {\n  return await fetchData()\n}\n```\n\nRequires `cacheComponents: true` in `next.config.ts`.\n\nFor detailed usage including cache profiles, `cacheLife()`, `cacheTag()`, and `updateTag()`, see the `next-cache-components` skill.\n\nReference: https://nextjs.org/docs/app/api-reference/directives/use-cache\n",".agents/skills/next-best-practices/error-handling.md":"# Error Handling\n\nHandle errors gracefully in Next.js applications.\n\nReference: https://nextjs.org/docs/app/getting-started/error-handling\n\n## Error Boundaries\n\n### `error.tsx`\n\nCatches errors in a route segment and its children:\n\n```tsx\n'use client'\n\nexport default function Error({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {\n  return (\n    <div>\n      <h2>Something went wrong!</h2>\n      <button onClick={() => reset()}>Try again</button>\n    </div>\n  )\n}\n```\n\n**Important:** `error.tsx` must be a Client Component.\n\n### `global-error.tsx`\n\nCatches errors in root layout:\n\n```tsx\n'use client'\n\nexport default function GlobalError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {\n  return (\n    <html>\n      <body>\n        <h2>Something went wrong!</h2>\n        <button onClick={() => reset()}>Try again</button>\n      </body>\n    </html>\n  )\n}\n```\n\n**Important:** Must include `<html>` and `<body>` tags.\n\n## Server Actions: Navigation API Gotcha\n\n**Do NOT wrap navigation APIs in try-catch.** They throw special errors that Next.js handles internally.\n\nReference: https://nextjs.org/docs/app/api-reference/functions/redirect#behavior\n\n```tsx\n'use server'\n\nimport { redirect } from 'next/navigation'\nimport { notFound } from 'next/navigation'\n\n// Bad: try-catch catches the navigation \"error\"\nasync function createPost(formData: FormData) {\n  try {\n    const post = await db.post.create({ ... })\n    redirect(`/posts/${post.id}`)  // This throws!\n  } catch (error) {\n    // redirect() throw is caught here - navigation fails!\n    return { error: 'Failed to create post' }\n  }\n}\n\n// Good: Call navigation APIs outside try-catch\nasync function createPost(formData: FormData) {\n  let post\n  try {\n    post = await db.post.create({ ... })\n  } catch (error) {\n    return { error: 'Failed to create post' }\n  }\n  redirect(`/posts/${post.id}`)  // Outside try-catch\n}\n\n// Good: Re-throw navigation errors\nasync function createPost(formData: FormData) {\n  try {\n    const post = await db.post.create({ ... })\n    redirect(`/posts/${post.id}`)\n  } catch (error) {\n    if (error instanceof Error && error.message === 'NEXT_REDIRECT') {\n      throw error  // Re-throw navigation errors\n    }\n    return { error: 'Failed to create post' }\n  }\n}\n```\n\nSame applies to:\n\n- `redirect()` - 307 temporary redirect\n- `permanentRedirect()` - 308 permanent redirect\n- `notFound()` - 404 not found\n- `forbidden()` - 403 forbidden\n- `unauthorized()` - 401 unauthorized\n\nUse `unstable_rethrow()` to re-throw these errors in catch blocks:\n\n```tsx\nimport { unstable_rethrow } from 'next/navigation'\n\nasync function action() {\n  try {\n    // ...\n    redirect('/success')\n  } catch (error) {\n    unstable_rethrow(error) // Re-throws Next.js internal errors\n    return { error: 'Something went wrong' }\n  }\n}\n```\n\n## Redirects\n\n```tsx\nimport { redirect, permanentRedirect } from 'next/navigation'\n\n// 307 Temporary - use for most cases\nredirect('/new-path')\n\n// 308 Permanent - use for URL migrations (cached by browsers)\npermanentRedirect('/new-url')\n```\n\n## Auth Errors\n\nTrigger auth-related error pages:\n\n```tsx\nimport { forbidden, unauthorized } from 'next/navigation'\n\nasync function Page() {\n  const session = await getSession()\n\n  if (!session) {\n    unauthorized() // Renders unauthorized.tsx (401)\n  }\n\n  if (!session.hasAccess) {\n    forbidden() // Renders forbidden.tsx (403)\n  }\n\n  return <Dashboard />\n}\n```\n\nCreate corresponding error pages:\n\n```tsx\n// app/forbidden.tsx\nexport default function Forbidden() {\n  return <div>You don't have access to this resource</div>\n}\n\n// app/unauthorized.tsx\nexport default function Unauthorized() {\n  return <div>Please log in to continue</div>\n}\n```\n\n## Not Found\n\n### `not-found.tsx`\n\nCustom 404 page for a route segment:\n\n```tsx\nexport default function NotFound() {\n  return (\n    <div>\n      <h2>Not Found</h2>\n      <p>Could not find the requested resource</p>\n    </div>\n  )\n}\n```\n\n### Triggering Not Found\n\n```tsx\nimport { notFound } from 'next/navigation'\n\nexport default async function Page({ params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n  const post = await getPost(id)\n\n  if (!post) {\n    notFound() // Renders closest not-found.tsx\n  }\n\n  return <div>{post.title}</div>\n}\n```\n\n## Error Hierarchy\n\nErrors bubble up to the nearest error boundary:\n\n```\napp/\n├── error.tsx           # Catches errors from all children\n├── blog/\n│   ├── error.tsx       # Catches errors in /blog/*\n│   └── [slug]/\n│       ├── error.tsx   # Catches errors in /blog/[slug]\n│       └── page.tsx\n└── layout.tsx          # Errors here go to global-error.tsx\n```\n",".agents/skills/next-best-practices/file-conventions.md":"# File Conventions\n\nNext.js App Router uses file-based routing with special file conventions.\n\n## Project Structure\n\nReference: https://nextjs.org/docs/app/getting-started/project-structure\n\n```\napp/\n├── layout.tsx          # Root layout (required)\n├── page.tsx            # Home page (/)\n├── loading.tsx         # Loading UI\n├── error.tsx           # Error UI\n├── not-found.tsx       # 404 UI\n├── global-error.tsx    # Global error UI\n├── route.ts            # API endpoint\n├── template.tsx        # Re-rendered layout\n├── default.tsx         # Parallel route fallback\n├── blog/\n│   ├── page.tsx        # /blog\n│   └── [slug]/\n│       └── page.tsx    # /blog/:slug\n└── (group)/            # Route group (no URL impact)\n    └── page.tsx\n```\n\n## Special Files\n\n| File            | Purpose                                  |\n| --------------- | ---------------------------------------- |\n| `page.tsx`      | UI for a route segment                   |\n| `layout.tsx`    | Shared UI for segment and children       |\n| `loading.tsx`   | Loading UI (Suspense boundary)           |\n| `error.tsx`     | Error UI (Error boundary)                |\n| `not-found.tsx` | 404 UI                                   |\n| `route.ts`      | API endpoint                             |\n| `template.tsx`  | Like layout but re-renders on navigation |\n| `default.tsx`   | Fallback for parallel routes             |\n\n## Route Segments\n\n```\napp/\n├── blog/               # Static segment: /blog\n├── [slug]/             # Dynamic segment: /:slug\n├── [...slug]/          # Catch-all: /a/b/c\n├── [[...slug]]/        # Optional catch-all: / or /a/b/c\n└── (marketing)/        # Route group (ignored in URL)\n```\n\n## Parallel Routes\n\n```\napp/\n├── @analytics/\n│   └── page.tsx\n├── @sidebar/\n│   └── page.tsx\n└── layout.tsx          # Receives { analytics, sidebar } as props\n```\n\n## Intercepting Routes\n\n```\napp/\n├── feed/\n│   └── page.tsx\n├── @modal/\n│   └── (.)photo/[id]/  # Intercepts /photo/[id] from /feed\n│       └── page.tsx\n└── photo/[id]/\n    └── page.tsx\n```\n\nConventions:\n\n- `(.)` - same level\n- `(..)` - one level up\n- `(..)(..)` - two levels up\n- `(...)` - from root\n\n## Private Folders\n\n```\napp/\n├── _components/        # Private folder (not a route)\n│   └── Button.tsx\n└── page.tsx\n```\n\nPrefix with `_` to exclude from routing.\n\n## Middleware / Proxy\n\n### Next.js 14-15: `middleware.ts`\n\n```ts\n// middleware.ts (root of project)\nimport { NextResponse } from 'next/server'\nimport type { NextRequest } from 'next/server'\n\nexport function middleware(request: NextRequest) {\n  // Auth, redirects, rewrites, etc.\n  return NextResponse.next()\n}\n\nexport const config = {\n  matcher: ['/dashboard/:path*', '/api/:path*'],\n}\n```\n\n### Next.js 16+: `proxy.ts`\n\nRenamed for clarity - same capabilities, different names:\n\n```ts\n// proxy.ts (root of project)\nimport { NextResponse } from 'next/server'\nimport type { NextRequest } from 'next/server'\n\nexport function proxy(request: NextRequest) {\n  // Same logic as middleware\n  return NextResponse.next()\n}\n\nexport const proxyConfig = {\n  matcher: ['/dashboard/:path*', '/api/:path*'],\n}\n```\n\n| Version | File            | Export         | Config        |\n| ------- | --------------- | -------------- | ------------- |\n| v14-15  | `middleware.ts` | `middleware()` | `config`      |\n| v16+    | `proxy.ts`      | `proxy()`      | `proxyConfig` |\n\n**Migration**: Run `npx @next/codemod@latest upgrade` to auto-rename.\n\n## File Conventions Reference\n\nReference: https://nextjs.org/docs/app/api-reference/file-conventions\n",".agents/skills/next-best-practices/font.md":"# Font Optimization\n\nUse `next/font` for automatic font optimization with zero layout shift.\n\n## Google Fonts\n\n```tsx\n// app/layout.tsx\nimport { Inter } from 'next/font/google'\n\nconst inter = Inter({ subsets: ['latin'] })\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\" className={inter.className}>\n      <body>{children}</body>\n    </html>\n  )\n}\n```\n\n## Multiple Fonts\n\n```tsx\nimport { Inter, Roboto_Mono } from 'next/font/google'\n\nconst inter = Inter({\n  subsets: ['latin'],\n  variable: '--font-inter',\n})\n\nconst robotoMono = Roboto_Mono({\n  subsets: ['latin'],\n  variable: '--font-roboto-mono',\n})\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\" className={`${inter.variable} ${robotoMono.variable}`}>\n      <body>{children}</body>\n    </html>\n  )\n}\n```\n\nUse in CSS:\n\n```css\nbody {\n  font-family: var(--font-inter);\n}\n\ncode {\n  font-family: var(--font-roboto-mono);\n}\n```\n\n## Font Weights and Styles\n\n```tsx\n// Single weight\nconst inter = Inter({\n  subsets: ['latin'],\n  weight: '400',\n})\n\n// Multiple weights\nconst inter = Inter({\n  subsets: ['latin'],\n  weight: ['400', '500', '700'],\n})\n\n// Variable font (recommended) - includes all weights\nconst inter = Inter({\n  subsets: ['latin'],\n  // No weight needed - variable fonts support all weights\n})\n\n// With italic\nconst inter = Inter({\n  subsets: ['latin'],\n  style: ['normal', 'italic'],\n})\n```\n\n## Local Fonts\n\n```tsx\nimport localFont from 'next/font/local'\n\nconst myFont = localFont({\n  src: './fonts/MyFont.woff2',\n})\n\n// Multiple files for different weights\nconst myFont = localFont({\n  src: [\n    {\n      path: './fonts/MyFont-Regular.woff2',\n      weight: '400',\n      style: 'normal',\n    },\n    {\n      path: './fonts/MyFont-Bold.woff2',\n      weight: '700',\n      style: 'normal',\n    },\n  ],\n})\n\n// Variable font\nconst myFont = localFont({\n  src: './fonts/MyFont-Variable.woff2',\n  variable: '--font-my-font',\n})\n```\n\n## Tailwind CSS Integration\n\n```tsx\n// app/layout.tsx\nimport { Inter } from 'next/font/google'\n\nconst inter = Inter({\n  subsets: ['latin'],\n  variable: '--font-inter',\n})\n\nexport default function RootLayout({ children }) {\n  return (\n    <html lang=\"en\" className={inter.variable}>\n      <body>{children}</body>\n    </html>\n  )\n}\n```\n\n```js\n// tailwind.config.js\nmodule.exports = {\n  theme: {\n    extend: {\n      fontFamily: {\n        sans: ['var(--font-inter)'],\n      },\n    },\n  },\n}\n```\n\n## Preloading Subsets\n\nOnly load needed character subsets:\n\n```tsx\n// Latin only (most common)\nconst inter = Inter({ subsets: ['latin'] })\n\n// Multiple subsets\nconst inter = Inter({ subsets: ['latin', 'latin-ext', 'cyrillic'] })\n```\n\n## Display Strategy\n\nControl font loading behavior:\n\n```tsx\nconst inter = Inter({\n  subsets: ['latin'],\n  display: 'swap', // Default - shows fallback, swaps when loaded\n})\n\n// Options:\n// 'auto' - browser decides\n// 'block' - short block period, then swap\n// 'swap' - immediate fallback, swap when ready (recommended)\n// 'fallback' - short block, short swap, then fallback\n// 'optional' - short block, no swap (use if font is optional)\n```\n\n## Don't Use Manual Font Links\n\nAlways use `next/font` instead of `<link>` tags for Google Fonts.\n\n```tsx\n// Bad: Manual link tag (blocks rendering, no optimization)\n<link href=\"https://fonts.googleapis.com/css2?family=Inter\" rel=\"stylesheet\" />\n\n// Bad: Missing display and preconnect\n<link href=\"https://fonts.googleapis.com/css2?family=Inter\" rel=\"stylesheet\" />\n\n// Good: Use next/font (self-hosted, zero layout shift)\nimport { Inter } from 'next/font/google'\n\nconst inter = Inter({ subsets: ['latin'] })\n```\n\n## Common Mistakes\n\n```tsx\n// Bad: Importing font in every component\n// components/Button.tsx\nimport { Inter } from 'next/font/google'\nconst inter = Inter({ subsets: ['latin'] }) // Creates new instance each time!\n\n// Good: Import once in layout, use CSS variable\n// app/layout.tsx\nconst inter = Inter({ subsets: ['latin'], variable: '--font-inter' })\n\n// Bad: Using @import in CSS (blocks rendering)\n/* globals.css */\n@import url('https://fonts.googleapis.com/css2?family=Inter');\n\n// Good: Use next/font (self-hosted, no network request)\nimport { Inter } from 'next/font/google'\n\n// Bad: Loading all weights when only using a few\nconst inter = Inter({ subsets: ['latin'] }) // Loads all weights\n\n// Good: Specify only needed weights (for non-variable fonts)\nconst inter = Inter({ subsets: ['latin'], weight: ['400', '700'] })\n\n// Bad: Missing subset - loads all characters\nconst inter = Inter({})\n\n// Good: Always specify subset\nconst inter = Inter({ subsets: ['latin'] })\n```\n\n## Font in Specific Components\n\n```tsx\n// For component-specific fonts, export from a shared file\n// lib/fonts.ts\nimport { Inter, Playfair_Display } from 'next/font/google'\n\nexport const inter = Inter({ subsets: ['latin'], variable: '--font-inter' })\nexport const playfair = Playfair_Display({ subsets: ['latin'], variable: '--font-playfair' })\n\n// components/Heading.tsx\nimport { playfair } from '@/lib/fonts'\n\nexport function Heading({ children }) {\n  return <h1 className={playfair.className}>{children}</h1>\n}\n```\n",".agents/skills/next-best-practices/functions.md":"# Functions\n\nNext.js function APIs.\n\nReference: https://nextjs.org/docs/app/api-reference/functions\n\n## Navigation Hooks (Client)\n\n| Hook                        | Purpose                                                        | Reference                                                                                |\n| --------------------------- | -------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |\n| `useRouter`                 | Programmatic navigation (`push`, `replace`, `back`, `refresh`) | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-router)                   |\n| `usePathname`               | Get current pathname                                           | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-pathname)                 |\n| `useSearchParams`           | Read URL search parameters                                     | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-search-params)            |\n| `useParams`                 | Access dynamic route parameters                                | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-params)                   |\n| `useSelectedLayoutSegment`  | Active child segment (one level)                               | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-selected-layout-segment)  |\n| `useSelectedLayoutSegments` | All active segments below layout                               | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-selected-layout-segments) |\n| `useLinkStatus`             | Check link prefetch status                                     | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-link-status)              |\n| `useReportWebVitals`        | Report Core Web Vitals metrics                                 | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-report-web-vitals)        |\n\n## Server Functions\n\n| Function     | Purpose                                      | Reference                                                              |\n| ------------ | -------------------------------------------- | ---------------------------------------------------------------------- |\n| `cookies`    | Read/write cookies                           | [Docs](https://nextjs.org/docs/app/api-reference/functions/cookies)    |\n| `headers`    | Read request headers                         | [Docs](https://nextjs.org/docs/app/api-reference/functions/headers)    |\n| `draftMode`  | Enable preview of unpublished CMS content    | [Docs](https://nextjs.org/docs/app/api-reference/functions/draft-mode) |\n| `after`      | Run code after response finishes streaming   | [Docs](https://nextjs.org/docs/app/api-reference/functions/after)      |\n| `connection` | Wait for connection before dynamic rendering | [Docs](https://nextjs.org/docs/app/api-reference/functions/connection) |\n| `userAgent`  | Parse User-Agent header                      | [Docs](https://nextjs.org/docs/app/api-reference/functions/userAgent)  |\n\n## Generate Functions\n\n| Function                | Purpose                                 | Reference                                                                           |\n| ----------------------- | --------------------------------------- | ----------------------------------------------------------------------------------- |\n| `generateStaticParams`  | Pre-render dynamic routes at build time | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-static-params)  |\n| `generateMetadata`      | Dynamic metadata                        | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-metadata)       |\n| `generateViewport`      | Dynamic viewport config                 | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-viewport)       |\n| `generateSitemaps`      | Multiple sitemaps for large sites       | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-sitemaps)       |\n| `generateImageMetadata` | Multiple OG images per route            | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-image-metadata) |\n\n## Request/Response\n\n| Function        | Purpose                        | Reference                                                                  |\n| --------------- | ------------------------------ | -------------------------------------------------------------------------- |\n| `NextRequest`   | Extended Request with helpers  | [Docs](https://nextjs.org/docs/app/api-reference/functions/next-request)   |\n| `NextResponse`  | Extended Response with helpers | [Docs](https://nextjs.org/docs/app/api-reference/functions/next-response)  |\n| `ImageResponse` | Generate OG images             | [Docs](https://nextjs.org/docs/app/api-reference/functions/image-response) |\n\n## Common Examples\n\n### Navigation\n\nUse `next/link` for internal navigation instead of `<a>` tags.\n\n```tsx\n// Bad: Plain anchor tag\n;<a href=\"/about\">About</a>\n\n// Good: Next.js Link\nimport Link from 'next/link'\n;<Link href=\"/about\">About</Link>\n```\n\nActive link styling:\n\n```tsx\n'use client'\n\nimport Link from 'next/link'\nimport { usePathname } from 'next/navigation'\n\nexport function NavLink({ href, children }) {\n  const pathname = usePathname()\n\n  return (\n    <Link href={href} className={pathname === href ? 'active' : ''}>\n      {children}\n    </Link>\n  )\n}\n```\n\n### Static Generation\n\n```tsx\n// app/blog/[slug]/page.tsx\nexport async function generateStaticParams() {\n  const posts = await getPosts()\n  return posts.map((post) => ({ slug: post.slug }))\n}\n```\n\n### After Response\n\n```tsx\nimport { after } from 'next/server'\n\nexport async function POST(request: Request) {\n  const data = await processRequest(request)\n\n  after(async () => {\n    await logAnalytics(data)\n  })\n\n  return Response.json({ success: true })\n}\n```\n",".agents/skills/next-best-practices/hydration-error.md":"# Hydration Errors\n\nDiagnose and fix React hydration mismatch errors.\n\n## Error Signs\n\n- \"Hydration failed because the initial UI does not match\"\n- \"Text content does not match server-rendered HTML\"\n\n## Debugging\n\nIn development, click the hydration error to see the server/client diff.\n\n## Common Causes and Fixes\n\n### Browser-only APIs\n\n```tsx\n// Bad: Causes mismatch - window doesn't exist on server\n;<div>{window.innerWidth}</div>\n\n// Good: Use client component with mounted check\n;('use client')\nimport { useState, useEffect } from 'react'\n\nexport function ClientOnly({ children }: { children: React.ReactNode }) {\n  const [mounted, setMounted] = useState(false)\n  useEffect(() => setMounted(true), [])\n  return mounted ? children : null\n}\n```\n\n### Date/Time Rendering\n\nServer and client may be in different timezones:\n\n```tsx\n// Bad: Causes mismatch\n;<span>{new Date().toLocaleString()}</span>\n\n// Good: Render on client only\n;('use client')\nconst [time, setTime] = useState<string>()\nuseEffect(() => setTime(new Date().toLocaleString()), [])\n```\n\n### Random Values or IDs\n\n```tsx\n// Bad: Random values differ between server and client\n<div id={Math.random().toString()}>\n\n// Good: Use useId hook\nimport { useId } from 'react'\n\nfunction Input() {\n  const id = useId()\n  return <input id={id} />\n}\n```\n\n### Invalid HTML Nesting\n\n```tsx\n// Bad: Invalid - div inside p\n<p><div>Content</div></p>\n\n// Bad: Invalid - p inside p\n<p><p>Nested</p></p>\n\n// Good: Valid nesting\n<div><p>Content</p></div>\n```\n\n### Third-party Scripts\n\nScripts that modify DOM during hydration.\n\n```tsx\n// Good: Use next/script with afterInteractive\nimport Script from 'next/script'\n\nexport default function Page() {\n  return <Script src=\"https://example.com/script.js\" strategy=\"afterInteractive\" />\n}\n```\n",".agents/skills/next-best-practices/image.md":"# Image Optimization\n\nUse `next/image` for automatic image optimization.\n\n## Always Use next/image\n\n```tsx\n// Bad: Avoid native img\n;<img src=\"/hero.png\" alt=\"Hero\" />\n\n// Good: Use next/image\nimport Image from 'next/image'\n;<Image src=\"/hero.png\" alt=\"Hero\" width={800} height={400} />\n```\n\n## Required Props\n\nImages need explicit dimensions to prevent layout shift:\n\n```tsx\n// Local images - dimensions inferred automatically\nimport heroImage from './hero.png'\n<Image src={heroImage} alt=\"Hero\" />\n\n// Remote images - must specify width/height\n<Image src=\"https://example.com/image.jpg\" alt=\"Hero\" width={800} height={400} />\n\n// Or use fill for parent-relative sizing\n<div style={{ position: 'relative', width: '100%', height: 400 }}>\n  <Image src=\"/hero.png\" alt=\"Hero\" fill style={{ objectFit: 'cover' }} />\n</div>\n```\n\n## Remote Images Configuration\n\nRemote domains must be configured in `next.config.js`:\n\n```js\n// next.config.js\nmodule.exports = {\n  images: {\n    remotePatterns: [\n      {\n        protocol: 'https',\n        hostname: 'example.com',\n        pathname: '/images/**',\n      },\n      {\n        protocol: 'https',\n        hostname: '*.cdn.com', // Wildcard subdomain\n      },\n    ],\n  },\n}\n```\n\n## Responsive Images\n\nUse `sizes` to tell the browser which size to download:\n\n```tsx\n// Full-width hero\n<Image\n  src=\"/hero.png\"\n  alt=\"Hero\"\n  fill\n  sizes=\"100vw\"\n/>\n\n// Responsive grid (3 columns on desktop, 1 on mobile)\n<Image\n  src=\"/card.png\"\n  alt=\"Card\"\n  fill\n  sizes=\"(max-width: 768px) 100vw, 33vw\"\n/>\n\n// Fixed sidebar image\n<Image\n  src=\"/avatar.png\"\n  alt=\"Avatar\"\n  width={200}\n  height={200}\n  sizes=\"200px\"\n/>\n```\n\n## Blur Placeholder\n\nPrevent layout shift with placeholders:\n\n```tsx\n// Local images - automatic blur hash\nimport heroImage from './hero.png'\n<Image src={heroImage} alt=\"Hero\" placeholder=\"blur\" />\n\n// Remote images - provide blurDataURL\n<Image\n  src=\"https://example.com/image.jpg\"\n  alt=\"Hero\"\n  width={800}\n  height={400}\n  placeholder=\"blur\"\n  blurDataURL=\"data:image/jpeg;base64,/9j/4AAQSkZJRg...\"\n/>\n\n// Or use color placeholder\n<Image\n  src=\"https://example.com/image.jpg\"\n  alt=\"Hero\"\n  width={800}\n  height={400}\n  placeholder=\"empty\"\n  style={{ backgroundColor: '#e0e0e0' }}\n/>\n```\n\n## Priority Loading\n\nUse `priority` for above-the-fold images (LCP):\n\n```tsx\n// Hero image - loads immediately\n<Image src=\"/hero.png\" alt=\"Hero\" fill priority />\n\n// Below-fold images - lazy loaded by default (no priority needed)\n<Image src=\"/card.png\" alt=\"Card\" width={400} height={300} />\n```\n\n## Common Mistakes\n\n```tsx\n// Bad: Missing sizes with fill - downloads largest image\n<Image src=\"/hero.png\" alt=\"Hero\" fill />\n\n// Good: Add sizes for proper responsive behavior\n<Image src=\"/hero.png\" alt=\"Hero\" fill sizes=\"100vw\" />\n\n// Bad: Using width/height for aspect ratio only\n<Image src=\"/hero.png\" alt=\"Hero\" width={16} height={9} />\n\n// Good: Use actual display dimensions or fill with sizes\n<Image src=\"/hero.png\" alt=\"Hero\" fill sizes=\"100vw\" style={{ objectFit: 'cover' }} />\n\n// Bad: Remote image without config\n<Image src=\"https://untrusted.com/image.jpg\" alt=\"Image\" width={400} height={300} />\n// Error: Invalid src prop, hostname not configured\n\n// Good: Add hostname to next.config.js remotePatterns\n```\n\n## Static Export\n\nWhen using `output: 'export'`, use `unoptimized` or custom loader:\n\n```tsx\n// Option 1: Disable optimization\n;<Image src=\"/hero.png\" alt=\"Hero\" width={800} height={400} unoptimized />\n\n// Option 2: Global config\n// next.config.js\nmodule.exports = {\n  output: 'export',\n  images: { unoptimized: true },\n}\n\n// Option 3: Custom loader (Cloudinary, Imgix, etc.)\nconst cloudinaryLoader = ({ src, width, quality }) => {\n  return `https://res.cloudinary.com/demo/image/upload/w_${width},q_${quality || 75}/${src}`\n}\n\n;<Image loader={cloudinaryLoader} src=\"sample.jpg\" alt=\"Sample\" width={800} height={400} />\n```\n",".agents/skills/next-best-practices/metadata.md":"# Metadata\n\nAdd SEO metadata to Next.js pages using the Metadata API.\n\n## Important: Server Components Only\n\nThe `metadata` object and `generateMetadata` function are **only supported in Server Components**. They cannot be used in Client Components.\n\nIf the target page has `'use client'`:\n\n1. Remove `'use client'` if possible, move client logic to child components\n2. Or extract metadata to a parent Server Component layout\n3. Or split the file: Server Component with metadata imports Client Components\n\n## Static Metadata\n\n```tsx\nimport type { Metadata } from 'next'\n\nexport const metadata: Metadata = {\n  title: 'Page Title',\n  description: 'Page description for search engines',\n}\n```\n\n## Dynamic Metadata\n\n```tsx\nimport type { Metadata } from 'next'\n\ntype Props = { params: Promise<{ slug: string }> }\n\nexport async function generateMetadata({ params }: Props): Promise<Metadata> {\n  const { slug } = await params\n  const post = await getPost(slug)\n  return { title: post.title, description: post.description }\n}\n```\n\n## Avoid Duplicate Fetches\n\nUse React `cache()` when the same data is needed for both metadata and page:\n\n```tsx\nimport { cache } from 'react'\n\nexport const getPost = cache(async (slug: string) => {\n  return await db.posts.findFirst({ where: { slug } })\n})\n```\n\n## Viewport\n\nSeparate from metadata for streaming support:\n\n```tsx\nimport type { Viewport } from 'next'\n\nexport const viewport: Viewport = {\n  width: 'device-width',\n  initialScale: 1,\n  themeColor: '#000000',\n}\n\n// Or dynamic\nexport function generateViewport({ params }): Viewport {\n  return { themeColor: getThemeColor(params) }\n}\n```\n\n## Title Templates\n\nIn root layout for consistent naming:\n\n```tsx\nexport const metadata: Metadata = {\n  title: { default: 'Site Name', template: '%s | Site Name' },\n}\n```\n\n## Metadata File Conventions\n\nReference: https://nextjs.org/docs/app/getting-started/project-structure#metadata-file-conventions\n\nPlace these files in `app/` directory (or route segments):\n\n| File                            | Purpose                                       |\n| ------------------------------- | --------------------------------------------- |\n| `favicon.ico`                   | Favicon                                       |\n| `icon.png` / `icon.svg`         | App icon                                      |\n| `apple-icon.png`                | Apple app icon                                |\n| `opengraph-image.png`           | OG image                                      |\n| `twitter-image.png`             | Twitter card image                            |\n| `sitemap.ts` / `sitemap.xml`    | Sitemap (use `generateSitemaps` for multiple) |\n| `robots.ts` / `robots.txt`      | Robots directives                             |\n| `manifest.ts` / `manifest.json` | Web app manifest                              |\n\n## SEO Best Practice: Static Files Are Often Enough\n\nFor most sites, **static metadata files provide excellent SEO coverage**:\n\n```\napp/\n├── favicon.ico\n├── opengraph-image.png     # Works for both OG and Twitter\n├── sitemap.ts\n├── robots.ts\n└── layout.tsx              # With title/description metadata\n```\n\n**Tips:**\n\n- A single `opengraph-image.png` covers both Open Graph and Twitter (Twitter falls back to OG)\n- Static `title` and `description` in layout metadata is sufficient for most pages\n- Only use dynamic `generateMetadata` when content varies per page\n\n---\n\n# OG Image Generation\n\nGenerate dynamic Open Graph images using `next/og`.\n\n## Important Rules\n\n1. **Use `next/og`** - not `@vercel/og` (it's built into Next.js)\n2. **No searchParams** - OG images can't access search params, use route params instead\n3. **Avoid Edge runtime** - Use default Node.js runtime\n\n```tsx\n// Good\nimport { ImageResponse } from 'next/og'\n\n// Bad\n// import { ImageResponse } from '@vercel/og'\n// export const runtime = 'edge'\n```\n\n## Basic OG Image\n\n```tsx\n// app/opengraph-image.tsx\nimport { ImageResponse } from 'next/og'\n\nexport const alt = 'Site Name'\nexport const size = { width: 1200, height: 630 }\nexport const contentType = 'image/png'\n\nexport default function Image() {\n  return new ImageResponse(\n    (\n      <div\n        style={{\n          fontSize: 128,\n          background: 'white',\n          width: '100%',\n          height: '100%',\n          display: 'flex',\n          alignItems: 'center',\n          justifyContent: 'center',\n        }}\n      >\n        Hello World\n      </div>\n    ),\n    { ...size },\n  )\n}\n```\n\n## Dynamic OG Image\n\n```tsx\n// app/blog/[slug]/opengraph-image.tsx\nimport { ImageResponse } from 'next/og'\n\nexport const alt = 'Blog Post'\nexport const size = { width: 1200, height: 630 }\nexport const contentType = 'image/png'\n\ntype Props = { params: Promise<{ slug: string }> }\n\nexport default async function Image({ params }: Props) {\n  const { slug } = await params\n  const post = await getPost(slug)\n\n  return new ImageResponse(\n    (\n      <div\n        style={{\n          fontSize: 48,\n          background: 'linear-gradient(to bottom, #1a1a1a, #333)',\n          color: 'white',\n          width: '100%',\n          height: '100%',\n          display: 'flex',\n          flexDirection: 'column',\n          alignItems: 'center',\n          justifyContent: 'center',\n          padding: 48,\n        }}\n      >\n        <div style={{ fontSize: 64, fontWeight: 'bold' }}>{post.title}</div>\n        <div style={{ marginTop: 24, opacity: 0.8 }}>{post.description}</div>\n      </div>\n    ),\n    { ...size },\n  )\n}\n```\n\n## Custom Fonts\n\n```tsx\nimport { ImageResponse } from 'next/og'\nimport { join } from 'path'\nimport { readFile } from 'fs/promises'\n\nexport default async function Image() {\n  const fontPath = join(process.cwd(), 'assets/fonts/Inter-Bold.ttf')\n  const fontData = await readFile(fontPath)\n\n  return new ImageResponse(<div style={{ fontFamily: 'Inter', fontSize: 64 }}>Custom Font Text</div>, {\n    width: 1200,\n    height: 630,\n    fonts: [{ name: 'Inter', data: fontData, style: 'normal' }],\n  })\n}\n```\n\n## File Naming\n\n- `opengraph-image.tsx` - Open Graph (Facebook, LinkedIn)\n- `twitter-image.tsx` - Twitter/X cards (optional, falls back to OG)\n\n## Styling Notes\n\nImageResponse uses Flexbox layout:\n\n- Use `display: 'flex'`\n- No CSS Grid support\n- Styles must be inline objects\n\n## Multiple OG Images\n\nUse `generateImageMetadata` for multiple images per route:\n\n```tsx\n// app/blog/[slug]/opengraph-image.tsx\nimport { ImageResponse } from 'next/og'\n\nexport async function generateImageMetadata({ params }) {\n  const images = await getPostImages(params.slug)\n  return images.map((img, idx) => ({\n    id: idx,\n    alt: img.alt,\n    size: { width: 1200, height: 630 },\n    contentType: 'image/png',\n  }))\n}\n\nexport default async function Image({ params, id }) {\n  const images = await getPostImages(params.slug)\n  const image = images[id]\n  return new ImageResponse(/* ... */)\n}\n```\n\n## Multiple Sitemaps\n\nUse `generateSitemaps` for large sites:\n\n```tsx\n// app/sitemap.ts\nimport type { MetadataRoute } from 'next'\n\nexport async function generateSitemaps() {\n  // Return array of sitemap IDs\n  return [{ id: 0 }, { id: 1 }, { id: 2 }]\n}\n\nexport default async function sitemap({ id }: { id: number }): Promise<MetadataRoute.Sitemap> {\n  const start = id * 50000\n  const end = start + 50000\n  const products = await getProducts(start, end)\n\n  return products.map((product) => ({\n    url: `https://example.com/product/${product.id}`,\n    lastModified: product.updatedAt,\n  }))\n}\n```\n\nGenerates `/sitemap/0.xml`, `/sitemap/1.xml`, etc.\n",".agents/skills/next-best-practices/parallel-routes.md":"# Parallel & Intercepting Routes\n\nParallel routes render multiple pages in the same layout. Intercepting routes show a different UI when navigating from within your app vs direct URL access. Together they enable modal patterns.\n\n## File Structure\n\n```\napp/\n├── @modal/                    # Parallel route slot\n│   ├── default.tsx            # Required! Returns null\n│   ├── (.)photos/             # Intercepts /photos/*\n│   │   └── [id]/\n│   │       └── page.tsx       # Modal content\n│   └── [...]catchall/         # Optional: catch unmatched\n│       └── page.tsx\n├── photos/\n│   └── [id]/\n│       └── page.tsx           # Full page (direct access)\n├── layout.tsx                 # Renders both children and @modal\n└── page.tsx\n```\n\n## Step 1: Root Layout with Slot\n\n```tsx\n// app/layout.tsx\nexport default function RootLayout({ children, modal }: { children: React.ReactNode; modal: React.ReactNode }) {\n  return (\n    <html>\n      <body>\n        {children}\n        {modal}\n      </body>\n    </html>\n  )\n}\n```\n\n## Step 2: Default File (Critical!)\n\n**Every parallel route slot MUST have a `default.tsx`** to prevent 404s on hard navigation.\n\n```tsx\n// app/@modal/default.tsx\nexport default function Default() {\n  return null\n}\n```\n\nWithout this file, refreshing any page will 404 because Next.js can't determine what to render in the `@modal` slot.\n\n## Step 3: Intercepting Route (Modal)\n\nThe `(.)` prefix intercepts routes at the same level.\n\n```tsx\n// app/@modal/(.)photos/[id]/page.tsx\nimport { Modal } from '@/components/modal'\n\nexport default async function PhotoModal({ params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n  const photo = await getPhoto(id)\n\n  return (\n    <Modal>\n      <img src={photo.url} alt={photo.title} />\n    </Modal>\n  )\n}\n```\n\n## Step 4: Full Page (Direct Access)\n\n```tsx\n// app/photos/[id]/page.tsx\nexport default async function PhotoPage({ params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n  const photo = await getPhoto(id)\n\n  return (\n    <div className=\"full-page\">\n      <img src={photo.url} alt={photo.title} />\n      <h1>{photo.title}</h1>\n    </div>\n  )\n}\n```\n\n## Step 5: Modal Component with Correct Closing\n\n**Critical: Use `router.back()` to close modals, NOT `router.push()` or `<Link>`.**\n\n```tsx\n// components/modal.tsx\n'use client'\n\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useRef } from 'react'\n\nexport function Modal({ children }: { children: React.ReactNode }) {\n  const router = useRouter()\n  const overlayRef = useRef<HTMLDivElement>(null)\n\n  // Close on escape key\n  useEffect(() => {\n    function onKeyDown(e: KeyboardEvent) {\n      if (e.key === 'Escape') {\n        router.back() // Correct\n      }\n    }\n    document.addEventListener('keydown', onKeyDown)\n    return () => document.removeEventListener('keydown', onKeyDown)\n  }, [router])\n\n  // Close on overlay click\n  const handleOverlayClick = useCallback(\n    (e: React.MouseEvent) => {\n      if (e.target === overlayRef.current) {\n        router.back() // Correct\n      }\n    },\n    [router],\n  )\n\n  return (\n    <div\n      ref={overlayRef}\n      onClick={handleOverlayClick}\n      className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\"\n    >\n      <div className=\"mx-4 w-full max-w-2xl rounded-lg bg-white p-6\">\n        <button\n          onClick={() => router.back()} // Correct!\n          className=\"absolute top-4 right-4\"\n        >\n          Close\n        </button>\n        {children}\n      </div>\n    </div>\n  )\n}\n```\n\n### Why NOT `router.push('/')` or `<Link href=\"/\">`?\n\nUsing `push` or `Link` to \"close\" a modal:\n\n1. Adds a new history entry (back button shows modal again)\n2. Doesn't properly clear the intercepted route\n3. Can cause the modal to flash or persist unexpectedly\n\n`router.back()` correctly:\n\n1. Removes the intercepted route from history\n2. Returns to the previous page\n3. Properly unmounts the modal\n\n## Route Matcher Reference\n\nMatchers match **route segments**, not filesystem paths:\n\n| Matcher    | Matches       | Example                                                               |\n| ---------- | ------------- | --------------------------------------------------------------------- |\n| `(.)`      | Same level    | `@modal/(.)photos` intercepts `/photos`                               |\n| `(..)`     | One level up  | `@modal/(..)settings` from `/dashboard/@modal` intercepts `/settings` |\n| `(..)(..)` | Two levels up | Rarely used                                                           |\n| `(...)`    | From root     | `@modal/(...)photos` intercepts `/photos` from anywhere               |\n\n**Common mistake**: Thinking `(..)` means \"parent folder\" - it means \"parent route segment\".\n\n## Handling Hard Navigation\n\nWhen users directly visit `/photos/123` (bookmark, refresh, shared link):\n\n- The intercepting route is bypassed\n- The full `photos/[id]/page.tsx` renders\n- Modal doesn't appear (expected behavior)\n\nIf you want the modal to appear on direct access too, you need additional logic:\n\n```tsx\n// app/photos/[id]/page.tsx\nimport { Modal } from '@/components/modal'\n\nexport default async function PhotoPage({ params }) {\n  const { id } = await params\n  const photo = await getPhoto(id)\n\n  // Option: Render as modal on direct access too\n  return (\n    <Modal>\n      <img src={photo.url} alt={photo.title} />\n    </Modal>\n  )\n}\n```\n\n## Common Gotchas\n\n### 1. Missing `default.tsx` → 404 on Refresh\n\nEvery `@slot` folder needs a `default.tsx` that returns `null` (or appropriate content).\n\n### 2. Modal Persists After Navigation\n\nYou're using `router.push()` instead of `router.back()`.\n\n### 3. Nested Parallel Routes Need Defaults Too\n\nIf you have `@modal` inside a route group, each level needs its own `default.tsx`:\n\n```\napp/\n├── (marketing)/\n│   ├── @modal/\n│   │   └── default.tsx     # Needed!\n│   └── layout.tsx\n└── layout.tsx\n```\n\n### 4. Intercepted Route Shows Wrong Content\n\nCheck your matcher:\n\n- `(.)photos` intercepts `/photos` from the same route level\n- If your `@modal` is in `app/dashboard/@modal`, use `(.)photos` to intercept `/dashboard/photos`, not `/photos`\n\n### 5. TypeScript Errors with `params`\n\nIn Next.js 15+, `params` is a Promise:\n\n```tsx\n// Correct\nexport default async function Page({ params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n}\n```\n\n## Complete Example: Photo Gallery Modal\n\n```\napp/\n├── @modal/\n│   ├── default.tsx\n│   └── (.)photos/\n│       └── [id]/\n│           └── page.tsx\n├── photos/\n│   ├── page.tsx           # Gallery grid\n│   └── [id]/\n│       └── page.tsx       # Full photo page\n├── layout.tsx\n└── page.tsx\n```\n\nLinks in the gallery:\n\n```tsx\n// app/photos/page.tsx\nimport Link from 'next/link'\n\nexport default async function Gallery() {\n  const photos = await getPhotos()\n\n  return (\n    <div className=\"grid grid-cols-3 gap-4\">\n      {photos.map((photo) => (\n        <Link key={photo.id} href={`/photos/${photo.id}`}>\n          <img src={photo.thumbnail} alt={photo.title} />\n        </Link>\n      ))}\n    </div>\n  )\n}\n```\n\nClicking a photo → Modal opens (intercepted)\nDirect URL → Full page renders\nRefresh while modal open → Full page renders\n",".agents/skills/next-best-practices/route-handlers.md":"# Route Handlers\n\nCreate API endpoints with `route.ts` files.\n\n## Basic Usage\n\n```tsx\n// app/api/users/route.ts\nexport async function GET() {\n  const users = await getUsers()\n  return Response.json(users)\n}\n\nexport async function POST(request: Request) {\n  const body = await request.json()\n  const user = await createUser(body)\n  return Response.json(user, { status: 201 })\n}\n```\n\n## Supported Methods\n\n`GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`\n\n## GET Handler Conflicts with page.tsx\n\n**A `route.ts` and `page.tsx` cannot coexist in the same folder.**\n\n```\napp/\n├── api/\n│   └── users/\n│       └── route.ts    # /api/users\n└── users/\n    ├── page.tsx        # /users (page)\n    └── route.ts        # Warning: Conflicts with page.tsx!\n```\n\nIf you need both a page and an API at the same path, use different paths:\n\n```\napp/\n├── users/\n│   └── page.tsx        # /users (page)\n└── api/\n    └── users/\n        └── route.ts    # /api/users (API)\n```\n\n## Environment Behavior\n\nRoute handlers run in a **Server Component-like environment**:\n\n- Yes: Can use `async/await`\n- Yes: Can access `cookies()`, `headers()`\n- Yes: Can use Node.js APIs\n- No: Cannot use React hooks\n- No: Cannot use React DOM APIs\n- No: Cannot use browser APIs\n\n```tsx\n// Bad: This won't work - no React DOM in route handlers\nimport { renderToString } from 'react-dom/server'\n\nexport async function GET() {\n  const html = renderToString(<Component />) // Error!\n  return new Response(html)\n}\n```\n\n## Dynamic Route Handlers\n\n```tsx\n// app/api/users/[id]/route.ts\nexport async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n  const user = await getUser(id)\n\n  if (!user) {\n    return Response.json({ error: 'Not found' }, { status: 404 })\n  }\n\n  return Response.json(user)\n}\n```\n\n## Request Helpers\n\n```tsx\nexport async function GET(request: Request) {\n  // URL and search params\n  const { searchParams } = new URL(request.url)\n  const query = searchParams.get('q')\n\n  // Headers\n  const authHeader = request.headers.get('authorization')\n\n  // Cookies (Next.js helper)\n  const cookieStore = await cookies()\n  const token = cookieStore.get('token')\n\n  return Response.json({ query, token })\n}\n```\n\n## Response Helpers\n\n```tsx\n// JSON response\nreturn Response.json({ data })\n\n// With status\nreturn Response.json({ error: 'Not found' }, { status: 404 })\n\n// With headers\nreturn Response.json(data, {\n  headers: {\n    'Cache-Control': 'max-age=3600',\n  },\n})\n\n// Redirect\nreturn Response.redirect(new URL('/login', request.url))\n\n// Stream\nreturn new Response(stream, {\n  headers: { 'Content-Type': 'text/event-stream' },\n})\n```\n\n## When to Use Route Handlers vs Server Actions\n\n| Use Case                 | Route Handlers | Server Actions |\n| ------------------------ | -------------- | -------------- |\n| Form submissions         | No             | Yes            |\n| Data mutations from UI   | No             | Yes            |\n| Third-party webhooks     | Yes            | No             |\n| External API consumption | Yes            | No             |\n| Public REST API          | Yes            | No             |\n| File uploads             | Both work      | Both work      |\n\n**Prefer Server Actions** for mutations triggered from your UI.\n**Use Route Handlers** for external integrations and public APIs.\n",".agents/skills/next-best-practices/rsc-boundaries.md":"# RSC Boundaries\n\nDetect and prevent invalid patterns when crossing Server/Client component boundaries.\n\n## Detection Rules\n\n### 1. Async Client Components Are Invalid\n\nClient components **cannot** be async functions. Only Server Components can be async.\n\n**Detect:** File has `'use client'` AND component is `async function` or returns `Promise`\n\n```tsx\n// Bad: async client component\n'use client'\nexport default async function UserProfile() {\n  const user = await getUser() // Cannot await in client component\n  return <div>{user.name}</div>\n}\n\n// Good: Remove async, fetch data in parent server component\n// page.tsx (server component - no 'use client')\nexport default async function Page() {\n  const user = await getUser()\n  return <UserProfile user={user} />\n}\n\n// UserProfile.tsx (client component)\n;('use client')\nexport function UserProfile({ user }: { user: User }) {\n  return <div>{user.name}</div>\n}\n```\n\n```tsx\n// Bad: async arrow function client component\n'use client'\nconst Dashboard = async () => {\n  const data = await fetchDashboard()\n  return <div>{data}</div>\n}\n\n// Good: Fetch in server component, pass data down\n```\n\n### 2. Non-Serializable Props to Client Components\n\nProps passed from Server → Client must be JSON-serializable.\n\n**Detect:** Server component passes these to a client component:\n\n- Functions (except Server Actions with `'use server'`)\n- `Date` objects\n- `Map`, `Set`, `WeakMap`, `WeakSet`\n- Class instances\n- `Symbol` (unless globally registered)\n- Circular references\n\n```tsx\n// Bad: Function prop\n// page.tsx (server)\nexport default function Page() {\n  const handleClick = () => console.log('clicked')\n  return <ClientButton onClick={handleClick} />\n}\n\n// Good: Define function inside client component\n// ClientButton.tsx\n;('use client')\nexport function ClientButton() {\n  const handleClick = () => console.log('clicked')\n  return <button onClick={handleClick}>Click</button>\n}\n```\n\n```tsx\n// Bad: Date object (silently becomes string, then crashes)\n// page.tsx (server)\nexport default async function Page() {\n  const post = await getPost()\n  return <PostCard createdAt={post.createdAt} /> // Date object\n}\n\n// PostCard.tsx (client) - will crash on .getFullYear()\n;('use client')\nexport function PostCard({ createdAt }: { createdAt: Date }) {\n  return <span>{createdAt.getFullYear()}</span> // Runtime error!\n}\n\n// Good: Serialize to string on server\n// page.tsx (server)\nexport default async function Page() {\n  const post = await getPost()\n  return <PostCard createdAt={post.createdAt.toISOString()} />\n}\n\n// PostCard.tsx (client)\n;('use client')\nexport function PostCard({ createdAt }: { createdAt: string }) {\n  const date = new Date(createdAt)\n  return <span>{date.getFullYear()}</span>\n}\n```\n\n```tsx\n// Bad: Class instance\nconst user = new UserModel(data)\n<ClientProfile user={user} /> // Methods will be stripped\n\n// Good: Pass plain object\nconst user = await getUser()\n<ClientProfile user={{ id: user.id, name: user.name }} />\n```\n\n```tsx\n// Bad: Map/Set\n<ClientComponent items={new Map([['a', 1]])} />\n\n// Good: Convert to array/object\n<ClientComponent items={Object.fromEntries(map)} />\n<ClientComponent items={Array.from(set)} />\n```\n\n### 3. Server Actions Are the Exception\n\nFunctions marked with `'use server'` CAN be passed to client components.\n\n```tsx\n// Valid: Server Action can be passed\n// actions.ts\n'use server'\nexport async function submitForm(formData: FormData) {\n  // server-side logic\n}\n\n// page.tsx (server)\nimport { submitForm } from './actions'\nexport default function Page() {\n  return <ClientForm onSubmit={submitForm} /> // OK!\n}\n\n// ClientForm.tsx (client)\n;('use client')\nexport function ClientForm({ onSubmit }: { onSubmit: (data: FormData) => Promise<void> }) {\n  return <form action={onSubmit}>...</form>\n}\n```\n\n## Quick Reference\n\n| Pattern                           | Valid? | Fix                                   |\n| --------------------------------- | ------ | ------------------------------------- |\n| `'use client'` + `async function` | No     | Fetch in server parent, pass data     |\n| Pass `() => {}` to client         | No     | Define in client or use server action |\n| Pass `new Date()` to client       | No     | Use `.toISOString()`                  |\n| Pass `new Map()` to client        | No     | Convert to object/array               |\n| Pass class instance to client     | No     | Pass plain object                     |\n| Pass server action to client      | Yes    | -                                     |\n| Pass `string/number/boolean`      | Yes    | -                                     |\n| Pass plain object/array           | Yes    | -                                     |\n",".agents/skills/next-best-practices/runtime-selection.md":"# Runtime Selection\n\n## Use Node.js Runtime by Default\n\nUse the default Node.js runtime for new routes and pages. Only use Edge runtime if the project already uses it or there's a specific requirement.\n\n```tsx\n// Good: Default - no runtime config needed (uses Node.js)\nexport default function Page() { ... }\n\n// Caution: Only if already used in project or specifically required\nexport const runtime = 'edge'\n```\n\n## When to Use Each\n\n### Node.js Runtime (Default)\n\n- Full Node.js API support\n- File system access (`fs`)\n- Full `crypto` support\n- Database connections\n- Most npm packages work\n\n### Edge Runtime\n\n- Only for specific edge-location latency requirements\n- Limited API (no `fs`, limited `crypto`)\n- Smaller cold start\n- Geographic distribution needs\n\n## Detection\n\n**Before adding `runtime = 'edge'`**, check:\n\n1. Does the project already use Edge runtime?\n2. Is there a specific latency requirement?\n3. Are all dependencies Edge-compatible?\n\nIf unsure, use Node.js runtime.\n",".agents/skills/next-best-practices/scripts.md":"# Scripts\n\nLoading third-party scripts in Next.js.\n\n## Use next/script\n\nAlways use `next/script` instead of native `<script>` tags for better performance.\n\n```tsx\n// Bad: Native script tag\n;<script src=\"https://example.com/script.js\"></script>\n\n// Good: Next.js Script component\nimport Script from 'next/script'\n;<Script src=\"https://example.com/script.js\" />\n```\n\n## Inline Scripts Need ID\n\nInline scripts require an `id` attribute for Next.js to track them.\n\n```tsx\n// Bad: Missing id\n<Script dangerouslySetInnerHTML={{ __html: 'console.log(\"hi\")' }} />\n\n// Good: Has id\n<Script id=\"my-script\" dangerouslySetInnerHTML={{ __html: 'console.log(\"hi\")' }} />\n\n// Good: Inline with id\n<Script id=\"show-banner\">\n  {`document.getElementById('banner').classList.remove('hidden')`}\n</Script>\n```\n\n## Don't Put Script in Head\n\n`next/script` should not be placed inside `next/head`. It handles its own positioning.\n\n```tsx\n// Bad: Script inside Head\nimport Head from 'next/head'\nimport Script from 'next/script'\n\n<Head>\n  <Script src=\"/analytics.js\" />\n</Head>\n\n// Good: Script outside Head\n<Head>\n  <title>Page</title>\n</Head>\n<Script src=\"/analytics.js\" />\n```\n\n## Loading Strategies\n\n```tsx\n// afterInteractive (default) - Load after page is interactive\n<Script src=\"/analytics.js\" strategy=\"afterInteractive\" />\n\n// lazyOnload - Load during idle time\n<Script src=\"/widget.js\" strategy=\"lazyOnload\" />\n\n// beforeInteractive - Load before page is interactive (use sparingly)\n// Only works in app/layout.tsx or pages/_document.js\n<Script src=\"/critical.js\" strategy=\"beforeInteractive\" />\n\n// worker - Load in web worker (experimental)\n<Script src=\"/heavy.js\" strategy=\"worker\" />\n```\n\n## Google Analytics\n\nUse `@next/third-parties` instead of inline GA scripts.\n\n```tsx\n// Bad: Inline GA script\n<Script src=\"https://www.googletagmanager.com/gtag/js?id=G-XXXXX\" />\n<Script id=\"ga-init\">\n  {`window.dataLayer = window.dataLayer || [];\n    function gtag(){dataLayer.push(arguments);}\n    gtag('js', new Date());\n    gtag('config', 'G-XXXXX');`}\n</Script>\n\n// Good: Next.js component\nimport { GoogleAnalytics } from '@next/third-parties/google'\n\nexport default function Layout({ children }) {\n  return (\n    <html>\n      <body>{children}</body>\n      <GoogleAnalytics gaId=\"G-XXXXX\" />\n    </html>\n  )\n}\n```\n\n## Google Tag Manager\n\n```tsx\nimport { GoogleTagManager } from '@next/third-parties/google'\n\nexport default function Layout({ children }) {\n  return (\n    <html>\n      <GoogleTagManager gtmId=\"GTM-XXXXX\" />\n      <body>{children}</body>\n    </html>\n  )\n}\n```\n\n## Other Third-Party Scripts\n\n```tsx\n// YouTube embed\nimport { YouTubeEmbed } from '@next/third-parties/google'\n;<YouTubeEmbed videoid=\"dQw4w9WgXcQ\" />\n\n// Google Maps\nimport { GoogleMapsEmbed } from '@next/third-parties/google'\n;<GoogleMapsEmbed apiKey=\"YOUR_API_KEY\" mode=\"place\" q=\"Brooklyn+Bridge,New+York,NY\" />\n```\n\n## Quick Reference\n\n| Pattern                                       | Issue                      | Fix                       |\n| --------------------------------------------- | -------------------------- | ------------------------- |\n| `<script src=\"...\">`                          | No optimization            | Use `next/script`         |\n| `<Script>` without id                         | Can't track inline scripts | Add `id` attribute        |\n| `<Script>` inside `<Head>`                    | Wrong placement            | Move outside Head         |\n| Inline GA/GTM scripts                         | No optimization            | Use `@next/third-parties` |\n| `strategy=\"beforeInteractive\"` outside layout | Won't work                 | Only use in root layout   |\n",".agents/skills/next-best-practices/self-hosting.md":"# Self-Hosting Next.js\n\nDeploy Next.js outside of Vercel with confidence.\n\n## Quick Start: Standalone Output\n\nFor Docker or any containerized deployment, use standalone output:\n\n```js\n// next.config.js\nmodule.exports = {\n  output: 'standalone',\n}\n```\n\nThis creates a minimal `standalone` folder with only production dependencies:\n\n```\n.next/\n├── standalone/\n│   ├── server.js          # Entry point\n│   ├── node_modules/      # Only production deps\n│   └── .next/             # Build output\n└── static/                # Must be copied separately\n```\n\n## Docker Deployment\n\n### Dockerfile\n\n```dockerfile\nFROM node:20-alpine AS base\n\n# Install dependencies\nFROM base AS deps\nWORKDIR /app\nCOPY package.json package-lock.json* ./\nRUN npm ci\n\n# Build\nFROM base AS builder\nWORKDIR /app\nCOPY --from=deps /app/node_modules ./node_modules\nCOPY . .\nRUN npm run build\n\n# Production\nFROM base AS runner\nWORKDIR /app\n\nENV NODE_ENV=production\n\n# Create non-root user\nRUN addgroup --system --gid 1001 nodejs\nRUN adduser --system --uid 1001 nextjs\n\n# Copy standalone output\nCOPY --from=builder /app/.next/standalone ./\nCOPY --from=builder /app/.next/static ./.next/static\nCOPY --from=builder /app/public ./public\n\nUSER nextjs\n\nEXPOSE 3000\nENV PORT=3000\nENV HOSTNAME=\"0.0.0.0\"\n\nCMD [\"node\", \"server.js\"]\n```\n\n### Docker Compose\n\n```yaml\nversion: '3.8'\n\nservices:\n  web:\n    build: .\n    ports:\n      - '3000:3000'\n    environment:\n      - NODE_ENV=production\n    restart: unless-stopped\n    healthcheck:\n      test: ['CMD', 'wget', '-q', '--spider', 'http://localhost:3000/api/health']\n      interval: 30s\n      timeout: 10s\n      retries: 3\n```\n\n## PM2 Deployment\n\nFor traditional server deployments:\n\n```js\n// ecosystem.config.js\nmodule.exports = {\n  apps: [\n    {\n      name: 'nextjs',\n      script: '.next/standalone/server.js',\n      instances: 'max',\n      exec_mode: 'cluster',\n      env: {\n        NODE_ENV: 'production',\n        PORT: 3000,\n      },\n    },\n  ],\n}\n```\n\n```bash\nnpm run build\npm2 start ecosystem.config.js\n```\n\n## ISR and Cache Handlers\n\n### The Problem\n\nISR (Incremental Static Regeneration) uses filesystem caching by default. This **breaks with multiple instances**:\n\n- Instance A regenerates page → saves to its local disk\n- Instance B serves stale page → doesn't see Instance A's cache\n- Load balancer sends users to random instances → inconsistent content\n\n### Solution: Custom Cache Handler\n\nNext.js 14+ supports custom cache handlers for shared storage:\n\n```js\n// next.config.js\nmodule.exports = {\n  cacheHandler: require.resolve('./cache-handler.js'),\n  cacheMaxMemorySize: 0, // Disable in-memory cache\n}\n```\n\n#### Redis Cache Handler Example\n\n```js\n// cache-handler.js\nconst Redis = require('ioredis')\n\nconst redis = new Redis(process.env.REDIS_URL)\nconst CACHE_PREFIX = 'nextjs:'\n\nmodule.exports = class CacheHandler {\n  constructor(options) {\n    this.options = options\n  }\n\n  async get(key) {\n    const data = await redis.get(CACHE_PREFIX + key)\n    if (!data) return null\n\n    const parsed = JSON.parse(data)\n    return {\n      value: parsed.value,\n      lastModified: parsed.lastModified,\n    }\n  }\n\n  async set(key, data, ctx) {\n    const cacheData = {\n      value: data,\n      lastModified: Date.now(),\n    }\n\n    // Set TTL based on revalidate option\n    if (ctx?.revalidate) {\n      await redis.setex(CACHE_PREFIX + key, ctx.revalidate, JSON.stringify(cacheData))\n    } else {\n      await redis.set(CACHE_PREFIX + key, JSON.stringify(cacheData))\n    }\n  }\n\n  async revalidateTag(tags) {\n    // Implement tag-based invalidation\n    // This requires tracking which keys have which tags\n  }\n}\n```\n\n#### S3 Cache Handler Example\n\n```js\n// cache-handler.js\nconst { S3Client, GetObjectCommand, PutObjectCommand } = require('@aws-sdk/client-s3')\n\nconst s3 = new S3Client({ region: process.env.AWS_REGION })\nconst BUCKET = process.env.CACHE_BUCKET\n\nmodule.exports = class CacheHandler {\n  async get(key) {\n    try {\n      const response = await s3.send(\n        new GetObjectCommand({\n          Bucket: BUCKET,\n          Key: `cache/${key}`,\n        }),\n      )\n      const body = await response.Body.transformToString()\n      return JSON.parse(body)\n    } catch (err) {\n      if (err.name === 'NoSuchKey') return null\n      throw err\n    }\n  }\n\n  async set(key, data, ctx) {\n    await s3.send(\n      new PutObjectCommand({\n        Bucket: BUCKET,\n        Key: `cache/${key}`,\n        Body: JSON.stringify({\n          value: data,\n          lastModified: Date.now(),\n        }),\n        ContentType: 'application/json',\n      }),\n    )\n  }\n}\n```\n\n## What Works vs What Needs Setup\n\n| Feature              | Single Instance | Multi-Instance      | Notes                       |\n| -------------------- | --------------- | ------------------- | --------------------------- |\n| SSR                  | Yes             | Yes                 | No special setup            |\n| SSG                  | Yes             | Yes                 | Built at deploy time        |\n| ISR                  | Yes             | Needs cache handler | Filesystem cache breaks     |\n| Image Optimization   | Yes             | Yes                 | CPU-intensive, consider CDN |\n| Middleware           | Yes             | Yes                 | Runs on Node.js             |\n| Edge Runtime         | Limited         | Limited             | Some features Node-only     |\n| `revalidatePath/Tag` | Yes             | Needs cache handler | Must share cache            |\n| `next/font`          | Yes             | Yes                 | Fonts bundled at build      |\n| Draft Mode           | Yes             | Yes                 | Cookie-based                |\n\n## Image Optimization\n\nNext.js Image Optimization works out of the box but is CPU-intensive.\n\n### Option 1: Built-in (Simple)\n\nWorks automatically, but consider:\n\n- Set `deviceSizes` and `imageSizes` in config to limit variants\n- Use `minimumCacheTTL` to reduce regeneration\n\n```js\n// next.config.js\nmodule.exports = {\n  images: {\n    minimumCacheTTL: 60 * 60 * 24, // 24 hours\n    deviceSizes: [640, 750, 1080, 1920], // Limit sizes\n  },\n}\n```\n\n### Option 2: External Loader (Recommended for Scale)\n\nOffload to Cloudinary, Imgix, or similar:\n\n```js\n// next.config.js\nmodule.exports = {\n  images: {\n    loader: 'custom',\n    loaderFile: './lib/image-loader.js',\n  },\n}\n```\n\n```js\n// lib/image-loader.js\nexport default function cloudinaryLoader({ src, width, quality }) {\n  const params = ['f_auto', 'c_limit', `w_${width}`, `q_${quality || 'auto'}`]\n  return `https://res.cloudinary.com/demo/image/upload/${params.join(',')}${src}`\n}\n```\n\n## Environment Variables\n\n### Build-time vs Runtime\n\n```js\n// Available at build time only (baked into bundle)\nNEXT_PUBLIC_API_URL=https://api.example.com\n\n// Available at runtime (server-side only)\nDATABASE_URL=postgresql://...\nAPI_SECRET=...\n```\n\n### Runtime Configuration\n\nFor truly dynamic config, don't use `NEXT_PUBLIC_*`. Instead:\n\n```tsx\n// app/api/config/route.ts\nexport async function GET() {\n  return Response.json({\n    apiUrl: process.env.API_URL,\n    features: process.env.FEATURES?.split(','),\n  })\n}\n```\n\n## OpenNext: Serverless Without Vercel\n\n[OpenNext](https://open-next.js.org/) adapts Next.js for AWS Lambda, Cloudflare Workers, etc.\n\n```bash\nnpx create-sst@latest\n# or\nnpx @opennextjs/aws build\n```\n\nSupports:\n\n- AWS Lambda + CloudFront\n- Cloudflare Workers\n- Netlify Functions\n- Deno Deploy\n\n## Health Check Endpoint\n\nAlways include a health check for load balancers:\n\n```tsx\n// app/api/health/route.ts\nexport async function GET() {\n  try {\n    // Optional: check database connection\n    // await db.$queryRaw`SELECT 1`;\n\n    return Response.json({ status: 'healthy' }, { status: 200 })\n  } catch (error) {\n    return Response.json({ status: 'unhealthy' }, { status: 503 })\n  }\n}\n```\n\n## Pre-Deployment Checklist\n\n1. **Build locally first**: `npm run build` - catch errors before deploy\n2. **Test standalone output**: `node .next/standalone/server.js`\n3. **Set `output: 'standalone'`** for Docker\n4. **Configure cache handler** for multi-instance ISR\n5. **Set `HOSTNAME=\"0.0.0.0\"`** for containers\n6. **Copy `public/` and `.next/static/`** - not included in standalone\n7. **Add health check endpoint**\n8. **Test ISR revalidation** after deployment\n9. **Monitor memory usage** - Node.js defaults may need tuning\n\n## Testing Cache Handler\n\n**Critical**: Test your cache handler on every Next.js upgrade:\n\n```bash\n# Start multiple instances\nPORT=3001 node .next/standalone/server.js &\nPORT=3002 node .next/standalone/server.js &\n\n# Trigger ISR revalidation\ncurl http://localhost:3001/api/revalidate?path=/posts\n\n# Verify both instances see the update\ncurl http://localhost:3001/posts\ncurl http://localhost:3002/posts\n# Should return identical content\n```\n",".agents/skills/next-best-practices/suspense-boundaries.md":"# Suspense Boundaries\n\nClient hooks that cause CSR bailout without Suspense boundaries.\n\n## useSearchParams\n\nAlways requires Suspense boundary in static routes. Without it, the entire page becomes client-side rendered.\n\n```tsx\n// Bad: Entire page becomes CSR\n'use client'\n\nimport { useSearchParams } from 'next/navigation'\n\nexport default function SearchBar() {\n  const searchParams = useSearchParams()\n  return <div>Query: {searchParams.get('q')}</div>\n}\n```\n\n```tsx\n// Good: Wrap in Suspense\nimport { Suspense } from 'react'\nimport SearchBar from './search-bar'\n\nexport default function Page() {\n  return (\n    <Suspense fallback={<div>Loading...</div>}>\n      <SearchBar />\n    </Suspense>\n  )\n}\n```\n\n## usePathname\n\nRequires Suspense boundary when route has dynamic parameters.\n\n```tsx\n// In dynamic route [slug]\n// Bad: No Suspense\n'use client'\nimport { usePathname } from 'next/navigation'\n\nexport function Breadcrumb() {\n  const pathname = usePathname()\n  return <nav>{pathname}</nav>\n}\n```\n\n```tsx\n// Good: Wrap in Suspense\n<Suspense fallback={<BreadcrumbSkeleton />}>\n  <Breadcrumb />\n</Suspense>\n```\n\nIf you use `generateStaticParams`, Suspense is optional.\n\n## Quick Reference\n\n| Hook                | Suspense Required    |\n| ------------------- | -------------------- |\n| `useSearchParams()` | Yes                  |\n| `usePathname()`     | Yes (dynamic routes) |\n| `useParams()`       | No                   |\n| `useRouter()`       | No                   |\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([fetchUser(), fetchConfig()])\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() {\n    return fetchUser()\n  },\n  async config() {\n    return fetchConfig()\n  },\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([userPromise, fetchConfig(), profilePromise])\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([configPromise, fetchData(session.user.id)])\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([fetchUser(), fetchPosts(), fetchComments()])\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({\n  enabled,\n  setEnabled,\n}: {\n  enabled: boolean\n  setEnabled: React.Dispatch<React.SetStateAction<boolean>>\n}) {\n  const [frames, setFrames] = useState<Frame[] | null>(null)\n\n  useEffect(() => {\n    if (enabled && !frames && typeof window !== 'undefined') {\n      import('./animation-frames.js').then((mod) => setFrames(mod.frames)).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(() => import('@vercel/analytics/react').then((m) => m.Analytics), { ssr: false })\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(() => import('./monaco-editor').then((m) => m.MonacoEditor), { ssr: false })\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 onMouseEnter={preload} onFocus={preload} onClick={onClick}>\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}>{children}</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    /* ... */\n  })\n  useKeyboardShortcut('k', () => {\n    /* ... */\n  })\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(\n      'prefs:v1',\n      JSON.stringify({\n        theme: user.preferences.theme,\n        notifications: user.preferences.notifications,\n      }),\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(\n    (newItems: Item[]) => {\n      setItems([...items, ...newItems])\n    },\n    [items],\n  ) // ❌ 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: MEDIUM (wasted computation on every render)**\n\nPass a functi\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\n   ```bash\n   pnpm install\n   ```\n\n2. Build AGENTS.md from rules:\n\n   ```bash\n   pnpm build\n   ```\n\n3. Validate rule files:\n\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\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/JimmyLv/BibiGPT-v1?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([configPromise, fetchData(session.user.id)])\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([fetchUser(), fetchConfig()])\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() {\n    return fetchUser()\n  },\n  async config() {\n    return fetchConfig()\n  },\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([userPromise, fetchConfig(), profilePromise])\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([fetchUser(), fetchPosts(), fetchComments()])\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({\n  enabled,\n  setEnabled,\n}: {\n  enabled: boolean\n  setEnabled: React.Dispatch<React.SetStateAction<boolean>>\n}) {\n  const [frames, setFrames] = useState<Frame[] | null>(null)\n\n  useEffect(() => {\n    if (enabled && !frames && typeof window !== 'undefined') {\n      import('./animation-frames.js').then((mod) => setFrames(mod.frames)).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(() => import('@vercel/analytics/react').then((m) => m.Analytics), { ssr: false })\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(() => import('./monaco-editor').then((m) => m.MonacoEditor), { ssr: false })\n\nfunction CodePanel({ code }: { code: string }) {\n  return <MonacoEditor value={code} />\n}\n```\n",".agents/skills/vercel-react-best-practices/rules/bundle-preload.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fbundle-preload.md -->",".agents/skills/vercel-react-best-practices/rules/client-event-listeners.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fclient-event-listeners.md -->",".agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fclient-localstorage-schema.md -->",".agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fclient-passive-event-listeners.md -->",".agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fclient-swr-dedup.md -->",".agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-batch-dom-css.md -->",".agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-cache-function-results.md -->",".agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-cache-property-access.md -->",".agents/skills/vercel-react-best-practices/rules/js-cache-storage.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-cache-storage.md -->",".agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-combine-iterations.md -->",".agents/skills/vercel-react-best-practices/rules/js-early-exit.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-early-exit.md -->",".agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-hoist-regexp.md -->",".agents/skills/vercel-react-best-practices/rules/js-index-maps.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-index-maps.md -->",".agents/skills/vercel-react-best-practices/rules/js-length-check-first.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-length-check-first.md -->",".agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-min-max-loop.md -->",".agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-serialization.md -->",".agents/skills/web-design-guidelines/SKILL.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fweb-design-guidelines%2FSKILL.md -->","AGENTS.md":"# Repository Guidelines\n\n## Project Structure & Module Organization\n\nThis repository is a Next.js + TypeScript app. Keep feature changes close to existing folders:\n\n- `pages/`: route entry points (`pages/api/` for server endpoints).\n- `components/`: UI and reusable view logic (`components/ui/` for primitive controls).\n- `hooks/`: custom React hooks.\n- `lib/`: integration logic (OpenAI, Supabase, Upstash, LemonSqueezy).\n- `utils/`: pure helpers and schema utilities (`utils/schemas/`).\n- `styles/`: global and markdown styles.\n- `public/`: static assets and images.\n\n## Build, Test, and Development Commands\n\nUse `npm` with the committed lockfile:\n\n- `npm ci`: install exact dependencies.\n- `npm run dev`: start local dev server at `http://localhost:3000`.\n- `npm run build`: production build (also catches type/build issues).\n- `npm run start`: run the built app.\n- `docker compose up -d`: run containerized service after creating `.env`.\n\nThere is no dedicated lint/test script. Use:\n\n- `npx prettier --check .`: formatting check.\n- `npx tsc --noEmit`: explicit TypeScript check.\n\n## Coding Style & Naming Conventions\n\n- TypeScript strict mode is enabled; prefer explicit, narrow types.\n- Formatting is enforced by Prettier (`singleQuote: true`, `semi: false`, `trailingComma: all`, `printWidth: 120`).\n- Pre-commit hook runs `lint-staged` with `prettier --write --ignore-unknown`.\n- Use naming patterns already present:\n  - Components: `PascalCase.tsx` (example: `SummaryResult.tsx`)\n  - Hooks: `useSomething.ts` (example: `useSummarize.ts`)\n  - Helpers: `camelCase.ts` (example: `extractUrl.ts`)\n- Prefer path aliases `@/` or `~/` for root imports.\n\n## Testing Guidelines\n\nNo automated test framework is configured yet. For every behavioral change, include manual verification notes in your PR:\n\n- Main summary flow from `pages/index.tsx`\n- Any affected API endpoint in `pages/api/`\n- At least one source parsing case (YouTube or Bilibili)\n\nIf adding tests, colocate them as `*.test.ts` or `*.test.tsx` near changed modules.\n\n## Commit & Pull Request Guidelines\n\n- Follow Conventional Commits used by release tooling: `feat:`, `fix:`, `docs:`, `chore:` (optional scope, e.g. `chore(main):`).\n- Keep commit subjects short and imperative.\n- PRs should include: purpose, key changes, config/env impact, linked issue(s), and UI screenshots when relevant.\n- Before requesting review, run `npm run build` and ensure formatting is clean.\n\n## Security & Configuration Tips\n\n- Copy `.example.env` to `.env`; never commit secrets.\n- Required core services are OpenAI and Upstash; Sentry/Lemon/Segment are optional.\n- Avoid sharing built Docker images that contain `.env`.\n"},"files":{".agents/skills/agent-browser/SKILL.md":"---\nname: agent-browser\ndescription: Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to \"open a website\", \"fill out a form\", \"click a button\", \"take a screenshot\", \"scrape data from a page\", \"test this web app\", \"login to a site\", \"automate browser actions\", or any task requiring programmatic web interaction.\nallowed-tools: Bash(npx agent-browser:*), Bash(agent-browser:*)\n---\n\n# Browser Automation with agent-browser\n\n## Core Workflow\n\nEvery browser automation follows this pattern:\n\n1. **Navigate**: `agent-browser open <url>`\n2. **Snapshot**: `agent-browser snapshot -i` (get element refs like `@e1`, `@e2`)\n3. **Interact**: Use refs to click, fill, select\n4. **Re-snapshot**: After navigation or DOM changes, get fresh refs\n\n```bash\nagent-browser open https://example.com/form\nagent-browser snapshot -i\n# Output: @e1 [input type=\"email\"], @e2 [input type=\"password\"], @e3 [button] \"Submit\"\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## Command Chaining\n\nCommands can be chained with `&&` in a single shell invocation. The browser persists between commands via a background daemon, so chaining is safe and more efficient than separate calls.\n\n```bash\n# Chain open + wait + snapshot in one call\nagent-browser open https://example.com && agent-browser wait --load networkidle && agent-browser snapshot -i\n\n# Chain multiple interactions\nagent-browser fill @e1 \"user@example.com\" && agent-browser fill @e2 \"password123\" && agent-browser click @e3\n\n# Navigate and capture\nagent-browser open https://example.com && agent-browser wait --load networkidle && agent-browser screenshot page.png\n```\n\n**When to chain:** Use `&&` when you don't need to read the output of an intermediate command before proceeding (e.g., open + wait + screenshot). Run commands separately when you need to parse the output first (e.g., snapshot to discover refs, then interact using those refs).\n\n## Essential Commands\n\n```bash\n# Navigation\nagent-browser open <url>              # Navigate (aliases: goto, navigate)\nagent-browser close                   # Close browser\n\n# Snapshot\nagent-browser snapshot -i             # Interactive elements with refs (recommended)\nagent-browser snapshot -i -C          # Include cursor-interactive elements (divs with onclick, cursor:pointer)\nagent-browser snapshot -s \"#selector\" # Scope to CSS selector\n\n# Interaction (use @refs from snapshot)\nagent-browser click @e1               # Click element\nagent-browser click @e1 --new-tab     # Click and open in new tab\nagent-browser fill @e2 \"text\"         # Clear and type text\nagent-browser type @e2 \"text\"         # Type without clearing\nagent-browser select @e1 \"option\"     # Select dropdown option\nagent-browser check @e1               # Check checkbox\nagent-browser press Enter             # Press key\nagent-browser keyboard type \"text\"    # Type at current focus (no selector)\nagent-browser keyboard inserttext \"text\"  # Insert without key events\nagent-browser scroll down 500         # Scroll page\nagent-browser scroll down 500 --selector \"div.content\"  # Scroll within a specific container\n\n# Get information\nagent-browser get text @e1            # Get element text\nagent-browser get url                 # Get current URL\nagent-browser get title               # Get page title\n\n# Wait\nagent-browser wait @e1                # Wait for element\nagent-browser wait --load networkidle # Wait for network idle\nagent-browser wait --url \"**/page\"    # Wait for URL pattern\nagent-browser wait 2000               # Wait milliseconds\n\n# Downloads\nagent-browser download @e1 ./file.pdf          # Click element to trigger download\nagent-browser wait --download ./output.zip     # Wait for any download to complete\nagent-browser --download-path ./downloads open <url>  # Set default download directory\n\n# Capture\nagent-browser screenshot              # Screenshot to temp dir\nagent-browser screenshot --full       # Full page screenshot\nagent-browser screenshot --annotate   # Annotated screenshot with numbered element labels\nagent-browser pdf output.pdf          # Save as PDF\n\n# Diff (compare page states)\nagent-browser diff snapshot                          # Compare current vs last snapshot\nagent-browser diff snapshot --baseline before.txt    # Compare current vs saved file\nagent-browser diff screenshot --baseline before.png  # Visual pixel diff\nagent-browser diff url <url1> <url2>                 # Compare two pages\nagent-browser diff url <url1> <url2> --wait-until networkidle  # Custom wait strategy\nagent-browser diff url <url1> <url2> --selector \"#main\"  # Scope to element\n```\n\n## Common Patterns\n\n### Form Submission\n\n```bash\nagent-browser open https://example.com/signup\nagent-browser snapshot -i\nagent-browser fill @e1 \"Jane Doe\"\nagent-browser fill @e2 \"jane@example.com\"\nagent-browser select @e3 \"California\"\nagent-browser check @e4\nagent-browser click @e5\nagent-browser wait --load networkidle\n```\n\n### Authentication with Auth Vault (Recommended)\n\n```bash\n# Save credentials once (encrypted with AGENT_BROWSER_ENCRYPTION_KEY)\n# Recommended: pipe password via stdin to avoid shell history exposure\necho \"pass\" | agent-browser auth save github --url https://github.com/login --username user --password-stdin\n\n# Login using saved profile (LLM never sees password)\nagent-browser auth login github\n\n# List/show/delete profiles\nagent-browser auth list\nagent-browser auth show github\nagent-browser auth delete github\n```\n\n### Authentication with State Persistence\n\n```bash\n# Login once and save state\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# Reuse in future sessions\nagent-browser state load auth.json\nagent-browser open https://app.example.com/dashboard\n```\n\n### Session Persistence\n\n```bash\n# Auto-save/restore cookies and localStorage across browser restarts\nagent-browser --session-name myapp open https://app.example.com/login\n# ... login flow ...\nagent-browser close  # State auto-saved to ~/.agent-browser/sessions/\n\n# Next time, state is auto-loaded\nagent-browser --session-name myapp open https://app.example.com/dashboard\n\n# Encrypt state at rest\nexport AGENT_BROWSER_ENCRYPTION_KEY=$(openssl rand -hex 32)\nagent-browser --session-name secure open https://app.example.com\n\n# Manage saved states\nagent-browser state list\nagent-browser state show myapp-default.json\nagent-browser state clear myapp\nagent-browser state clean --older-than 7\n```\n\n### Data Extraction\n\n```bash\nagent-browser open https://example.com/products\nagent-browser snapshot -i\nagent-browser get text @e5           # Get specific element text\nagent-browser get text body > page.txt  # Get all page text\n\n# JSON output for parsing\nagent-browser snapshot -i --json\nagent-browser get text @e1 --json\n```\n\n### Parallel Sessions\n\n```bash\nagent-browser --session site1 open https://site-a.com\nagent-browser --session site2 open https://site-b.com\n\nagent-browser --session site1 snapshot -i\nagent-browser --session site2 snapshot -i\n\nagent-browser session list\n```\n\n### Connect to Existing Chrome\n\n```bash\n# Auto-discover running Chrome with remote debugging enabled\nagent-browser --auto-connect open https://example.com\nagent-browser --auto-connect snapshot\n\n# Or with explicit CDP port\nagent-browser --cdp 9222 snapshot\n```\n\n### Color Scheme (Dark Mode)\n\n```bash\n# Persistent dark mode via flag (applies to all pages and new tabs)\nagent-browser --color-scheme dark open https://example.com\n\n# Or via environment variable\nAGENT_BROWSER_COLOR_SCHEME=dark agent-browser open https://example.com\n\n# Or set during session (persists for subsequent commands)\nagent-browser set media dark\n```\n\n### Visual Browser (Debugging)\n\n```bash\nagent-browser --headed open https://example.com\nagent-browser highlight @e1          # Highlight element\nagent-browser record start demo.webm # Record session\nagent-browser profiler start         # Start Chrome DevTools profiling\nagent-browser profiler stop trace.json # Stop and save profile (path optional)\n```\n\nUse `AGENT_BROWSER_HEADED=1` to enable headed mode via environment variable. Browser extensions work in both headed and headless mode.\n\n### Local Files (PDFs, HTML)\n\n```bash\n# Open local files with file:// URLs\nagent-browser --allow-file-access open file:///path/to/document.pdf\nagent-browser --allow-file-access open file:///path/to/page.html\nagent-browser screenshot output.png\n```\n\n### iOS Simulator (Mobile Safari)\n\n```bash\n# List available iOS simulators\nagent-browser device list\n\n# Launch Safari on a specific device\nagent-browser -p ios --device \"iPhone 16 Pro\" open https://example.com\n\n# Same workflow as desktop - snapshot, interact, re-snapshot\nagent-browser -p ios snapshot -i\nagent-browser -p ios tap @e1          # Tap (alias for click)\nagent-browser -p ios fill @e2 \"text\"\nagent-browser -p ios swipe up         # Mobile-specific gesture\n\n# Take screenshot\nagent-browser -p ios screenshot mobile.png\n\n# Close session (shuts down simulator)\nagent-browser -p ios close\n```\n\n**Requirements:** macOS with Xcode, Appium (`npm install -g appium && appium driver install xcuitest`)\n\n**Real devices:** Works with physical iOS devices if pre-configured. Use `--device \"<UDID>\"` where UDID is from `xcrun xctrace list devices`.\n\n## Security\n\nAll security features are opt-in. By default, agent-browser imposes no restrictions on navigation, actions, or output.\n\n### Content Boundaries (Recommended for AI Agents)\n\nEnable `--content-boundaries` to wrap page-sourced output in markers that help LLMs distinguish tool output from untrusted page content:\n\n```bash\nexport AGENT_BROWSER_CONTENT_BOUNDARIES=1\nagent-browser snapshot\n# Output:\n# --- AGENT_BROWSER_PAGE_CONTENT nonce=<hex> origin=https://example.com ---\n# [accessibility tree]\n# --- END_AGENT_BROWSER_PAGE_CONTENT nonce=<hex> ---\n```\n\n### Domain Allowlist\n\nRestrict navigation to trusted domains. Wildcards like `*.example.com` also match the bare domain `example.com`. Sub-resource requests, WebSocket, and EventSource connections to non-allowed domains are also blocked. Include CDN domains your target pages depend on:\n\n```bash\nexport AGENT_BROWSER_ALLOWED_DOMAINS=\"example.com,*.example.com\"\nagent-browser open https://example.com        # OK\nagent-browser open https://malicious.com       # Blocked\n```\n\n### Action Policy\n\nUse a policy file to gate destructive actions:\n\n```bash\nexport AGENT_BROWSER_ACTION_POLICY=./policy.json\n```\n\nExample `policy.json`:\n\n```json\n{ \"default\": \"deny\", \"allow\": [\"navigate\", \"snapshot\", \"click\", \"scroll\", \"wait\", \"get\"] }\n```\n\nAuth vault operations (`auth login`, etc.) bypass action policy but domain allowlist still applies.\n\n### Output Limits\n\nPrevent context flooding from large pages:\n\n```bash\nexport AGENT_BROWSER_MAX_OUTPUT=50000\n```\n\n## Diffing (Verifying Changes)\n\nUse `diff snapshot` after performing an action to verify it had the intended effect. This compares the current accessibility tree against the last snapshot taken in the session.\n\n```bash\n# Typical workflow: snapshot -> action -> diff\nagent-browser snapshot -i          # Take baseline snapshot\nagent-browser click @e2            # Perform action\nagent-browser diff snapshot        # See what changed (auto-compares to last snapshot)\n```\n\nFor visual regression testing or monitoring:\n\n```bash\n# Save a baseline screenshot, then compare later\nagent-browser screenshot baseline.png\n# ... time passes or changes are made ...\nagent-browser diff screenshot --baseline baseline.png\n\n# Compare staging vs production\nagent-browser diff url https://staging.example.com https://prod.example.com --screenshot\n```\n\n`diff snapshot` output uses `+` for additions and `-` for removals, similar to git diff. `diff screenshot` produces a diff image with changed pixels highlighted in red, plus a mismatch percentage.\n\n## Timeouts and Slow Pages\n\nThe default Playwright timeout is 25 seconds for local browsers. This can be overridden with the `AGENT_BROWSER_DEFAULT_TIMEOUT` environment variable (value in milliseconds). For slow websites or large pages, use explicit waits instead of relying on the default timeout:\n\n```bash\n# Wait for network activity to settle (best for slow pages)\nagent-browser wait --load networkidle\n\n# Wait for a specific element to appear\nagent-browser wait \"#content\"\nagent-browser wait @e1\n\n# Wait for a specific URL pattern (useful after redirects)\nagent-browser wait --url \"**/dashboard\"\n\n# Wait for a JavaScript condition\nagent-browser wait --fn \"document.readyState === 'complete'\"\n\n# Wait a fixed duration (milliseconds) as a last resort\nagent-browser wait 5000\n```\n\nWhen dealing with consistently slow websites, use `wait --load networkidle` after `open` to ensure the page is fully loaded before taking a snapshot. If a specific element is slow to render, wait for it directly with `wait <selector>` or `wait @ref`.\n\n## Session Management and Cleanup\n\nWhen running multiple agents or automations concurrently, always use named sessions to avoid conflicts:\n\n```bash\n# Each agent gets its own isolated session\nagent-browser --session agent1 open site-a.com\nagent-browser --session agent2 open site-b.com\n\n# Check active sessions\nagent-browser session list\n```\n\nAlways close your browser session when done to avoid leaked processes:\n\n```bash\nagent-browser close                    # Close default session\nagent-browser --session agent1 close   # Close specific session\n```\n\nIf a previous session was not closed properly, the daemon may still be running. Use `agent-browser close` to clean it up before starting new work.\n\n## Ref Lifecycle (Important)\n\nRefs (`@e1`, `@e2`, etc.) are invalidated when the page changes. Always re-snapshot after:\n\n- Clicking links or buttons that navigate\n- Form submissions\n- Dynamic content loading (dropdowns, modals)\n\n```bash\nagent-browser click @e5              # Navigates to new page\nagent-browser snapshot -i            # MUST re-snapshot\nagent-browser click @e1              # Use new refs\n```\n\n## Annotated Screenshots (Vision Mode)\n\nUse `--annotate` to take a screenshot with numbered labels overlaid on interactive elements. Each label `[N]` maps to ref `@eN`. This also caches refs, so you can interact with elements immediately without a separate snapshot.\n\n```bash\nagent-browser screenshot --annotate\n# Output includes the image path and a legend:\n#   [1] @e1 button \"Submit\"\n#   [2] @e2 link \"Home\"\n#   [3] @e3 textbox \"Email\"\nagent-browser click @e2              # Click using ref from annotated screenshot\n```\n\nUse annotated screenshots when:\n\n- The page has unlabeled icon buttons or visual-only elements\n- You need to verify visual layout or styling\n- Canvas or chart elements are present (invisible to text snapshots)\n- You need spatial reasoning about element positions\n\n## Semantic Locators (Alternative to Refs)\n\nWhen refs are unavailable or unreliable, use semantic locators:\n\n```bash\nagent-browser find text \"Sign In\" click\nagent-browser find label \"Email\" fill \"user@test.com\"\nagent-browser find role button click --name \"Submit\"\nagent-browser find placeholder \"Search\" type \"query\"\nagent-browser find testid \"submit-btn\" click\n```\n\n## JavaScript Evaluation (eval)\n\nUse `eval` to run JavaScript in the browser context. **Shell quoting can corrupt complex expressions** -- use `--stdin` or `-b` to avoid issues.\n\n```bash\n# Simple expressions work with regular quoting\nagent-browser eval 'document.title'\nagent-browser eval 'document.querySelectorAll(\"img\").length'\n\n# Complex JS: use --stdin with heredoc (RECOMMENDED)\nagent-browser eval --stdin <<'EVALEOF'\nJSON.stringify(\n  Array.from(document.querySelectorAll(\"img\"))\n    .filter(i => !i.alt)\n    .map(i => ({ src: i.src.split(\"/\").pop(), width: i.width }))\n)\nEVALEOF\n\n# Alternative: base64 encoding (avoids all shell escaping issues)\nagent-browser eval -b \"$(echo -n 'Array.from(document.querySelectorAll(\"a\")).map(a => a.href)' | base64)\"\n```\n\n**Why this matters:** When the shell processes your command, inner double quotes, `!` characters (history expansion), backticks, and `$()` can all corrupt the JavaScript before it reaches agent-browser. The `--stdin` and `-b` flags bypass shell interpretation entirely.\n\n**Rules of thumb:**\n\n- Single-line, no nested quotes -> regular `eval 'expression'` with single quotes is fine\n- Nested quotes, arrow functions, template literals, or multiline -> use `eval --stdin <<'EVALEOF'`\n- Programmatic/generated scripts -> use `eval -b` with base64\n\n## Configuration File\n\nCreate `agent-browser.json` in the project root for persistent settings:\n\n```json\n{\n  \"headed\": true,\n  \"proxy\": \"http://localhost:8080\",\n  \"profile\": \"./browser-data\"\n}\n```\n\nPriority (lowest to highest): `~/.agent-browser/config.json` < `./agent-browser.json` < env vars < CLI flags. Use `--config <path>` or `AGENT_BROWSER_CONFIG` env var for a custom config file (exits with error if missing/invalid). All CLI options map to camelCase keys (e.g., `--executable-path` -> `\"executablePath\"`). Boolean flags accept `true`/`false` values (e.g., `--headed false` overrides config). Extensions from user and project configs are merged, not replaced.\n\n## Deep-Dive Documentation\n\n| Reference                                                            | When to Use                                               |\n| -------------------------------------------------------------------- | --------------------------------------------------------- |\n| [references/commands.md](references/commands.md)                     | Full command reference with all options                   |\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/profiling.md](references/profiling.md)                   | Chrome DevTools profiling for performance analysis        |\n| [references/proxy-support.md](references/proxy-support.md)           | Proxy configuration, geo-testing, rotating proxies        |\n\n## Experimental: Native Mode\n\nagent-browser has an experimental native Rust daemon that communicates with Chrome directly via CDP, bypassing Node.js and Playwright entirely. It is opt-in and not recommended for production use yet.\n\n```bash\n# Enable via flag\nagent-browser --native open example.com\n\n# Enable via environment variable (avoids passing --native every time)\nexport AGENT_BROWSER_NATIVE=1\nagent-browser open example.com\n```\n\nThe native daemon supports Chromium and Safari (via WebDriver). Firefox and WebKit are not yet supported. All core commands (navigate, snapshot, click, fill, screenshot, cookies, storage, tabs, eval, etc.) work identically in native mode. Use `agent-browser close` before switching between native and default mode within the same session.\n\n## Ready-to-Use Templates\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\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",".agents/skills/agent-browser/references/authentication.md":"# Authentication Patterns\n\nLogin flows, session persistence, OAuth, 2FA, and authenticated browsing.\n\n**Related**: [session-management.md](session-management.md) for state persistence details, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [Basic Login Flow](#basic-login-flow)\n- [Saving Authentication State](#saving-authentication-state)\n- [Restoring Authentication](#restoring-authentication)\n- [OAuth / SSO Flows](#oauth--sso-flows)\n- [Two-Factor Authentication](#two-factor-authentication)\n- [HTTP Basic Auth](#http-basic-auth)\n- [Cookie-Based Auth](#cookie-based-auth)\n- [Token Refresh Handling](#token-refresh-handling)\n- [Security Best Practices](#security-best-practices)\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\n   ```bash\n   echo \"*.auth-state.json\" >> .gitignore\n   ```\n\n2. **Use environment variables for credentials**\n\n   ```bash\n   agent-browser fill @e1 \"$APP_USERNAME\"\n   agent-browser fill @e2 \"$APP_PASSWORD\"\n   ```\n\n3. **Clean up after automation**\n\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/commands.md":"# Command Reference\n\nComplete reference for all agent-browser commands. For quick start and common patterns, see SKILL.md.\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 click @e1 --new-tab # Click and open in new tab\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 and PDF\n\n```bash\nagent-browser screenshot          # Save to temporary directory\nagent-browser screenshot path.png # Save to 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\nagent-browser click @e1                   # Perform actions\nagent-browser record stop                 # Stop and save video\nagent-browser record restart ./take2.webm # Stop current + start new\n```\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 and 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 and 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\"          # Simple expressions only\nagent-browser eval -b \"<base64>\"             # Any JavaScript (base64 encoded)\nagent-browser eval --stdin                   # Read script from stdin\n```\n\nUse `-b`/`--base64` or `--stdin` for reliable execution. Shell escaping with nested quotes and special characters is error-prone.\n\n```bash\n# Base64 encode your script, then:\nagent-browser eval -b \"ZG9jdW1lbnQucXVlcnlTZWxlY3RvcignW3NyYyo9Il9uZXh0Il0nKQ==\"\n\n# Or use stdin with heredoc for multiline scripts:\ncat <<'EOF' | agent-browser eval --stdin\nconst links = document.querySelectorAll('a');\nArray.from(links).map(a => a.href);\nEOF\n```\n\n## State Management\n\n```bash\nagent-browser state save auth.json    # Save cookies, storage, auth state\nagent-browser state load auth.json    # Restore saved state\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 --proxy-bypass <hosts>  # Hosts to bypass proxy\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 --ignore-https-errors   # Ignore SSL certificate errors\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## 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 profiler start              # Start Chrome DevTools profiling\nagent-browser profiler stop trace.json    # Stop and save profile\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=\"browserbase\"         # Cloud browser provider\nAGENT_BROWSER_STREAM_PORT=\"9223\"             # WebSocket streaming port\nAGENT_BROWSER_HOME=\"/path/to/agent-browser\"  # Custom install location\n```\n",".agents/skills/agent-browser/references/profiling.md":"# Profiling\n\nCapture Chrome DevTools performance profiles during browser automation for performance analysis.\n\n**Related**: [commands.md](commands.md) for full command reference, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [Basic Profiling](#basic-profiling)\n- [Profiler Commands](#profiler-commands)\n- [Categories](#categories)\n- [Use Cases](#use-cases)\n- [Output Format](#output-format)\n- [Viewing Profiles](#viewing-profiles)\n- [Limitations](#limitations)\n\n## Basic Profiling\n\n```bash\n# Start profiling\nagent-browser profiler start\n\n# Perform actions\nagent-browser navigate https://example.com\nagent-browser click \"#button\"\nagent-browser wait 1000\n\n# Stop and save\nagent-browser profiler stop ./trace.json\n```\n\n## Profiler Commands\n\n```bash\n# Start profiling with default categories\nagent-browser profiler start\n\n# Start with custom trace categories\nagent-browser profiler start --categories \"devtools.timeline,v8.execute,blink.user_timing\"\n\n# Stop profiling and save to file\nagent-browser profiler stop ./trace.json\n```\n\n## Categories\n\nThe `--categories` flag accepts a comma-separated list of Chrome trace categories. Default categories include:\n\n- `devtools.timeline` -- standard DevTools performance traces\n- `v8.execute` -- time spent running JavaScript\n- `blink` -- renderer events\n- `blink.user_timing` -- `performance.mark()` / `performance.measure()` calls\n- `latencyInfo` -- input-to-latency tracking\n- `renderer.scheduler` -- task scheduling and execution\n- `toplevel` -- broad-spectrum basic events\n\nSeveral `disabled-by-default-*` categories are also included for detailed timeline, call stack, and V8 CPU profiling data.\n\n## Use Cases\n\n### Diagnosing Slow Page Loads\n\n```bash\nagent-browser profiler start\nagent-browser navigate https://app.example.com\nagent-browser wait --load networkidle\nagent-browser profiler stop ./page-load-profile.json\n```\n\n### Profiling User Interactions\n\n```bash\nagent-browser navigate https://app.example.com\nagent-browser profiler start\nagent-browser click \"#submit\"\nagent-browser wait 2000\nagent-browser profiler stop ./interaction-profile.json\n```\n\n### CI Performance Regression Checks\n\n```bash\n#!/bin/bash\nagent-browser profiler start\nagent-browser navigate https://app.example.com\nagent-browser wait --load networkidle\nagent-browser profiler stop \"./profiles/build-${BUILD_ID}.json\"\n```\n\n## Output Format\n\nThe output is a JSON file in Chrome Trace Event format:\n\n```json\n{\n  \"traceEvents\": [\n    { \"cat\": \"devtools.timeline\", \"name\": \"RunTask\", \"ph\": \"X\", \"ts\": 12345, \"dur\": 100, ... },\n    ...\n  ],\n  \"metadata\": {\n    \"clock-domain\": \"LINUX_CLOCK_MONOTONIC\"\n  }\n}\n```\n\nThe `metadata.clock-domain` field is set based on the host platform (Linux or macOS). On Windows it is omitted.\n\n## Viewing Profiles\n\nLoad the output JSON file in any of these tools:\n\n- **Chrome DevTools**: Performance panel > Load profile (Ctrl+Shift+I > Performance)\n- **Perfetto UI**: https://ui.perfetto.dev/ -- drag and drop the JSON file\n- **Trace Viewer**: `chrome://tracing` in any Chromium browser\n\n## Limitations\n\n- Only works with Chromium-based browsers (Chrome, Edge). Not supported on Firefox or WebKit.\n- Trace data accumulates in memory while profiling is active (capped at 5 million events). Stop profiling promptly after the area of interest.\n- Data collection on stop has a 30-second timeout. If the browser is unresponsive, the stop command may fail.\n",".agents/skills/agent-browser/references/proxy-support.md":"# Proxy Support\n\nProxy configuration for geo-testing, rate limiting avoidance, and corporate environments.\n\n**Related**: [commands.md](commands.md) for global options, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [Basic Proxy Configuration](#basic-proxy-configuration)\n- [Authenticated Proxy](#authenticated-proxy)\n- [SOCKS Proxy](#socks-proxy)\n- [Proxy Bypass](#proxy-bypass)\n- [Common Use Cases](#common-use-cases)\n- [Verifying Proxy Connection](#verifying-proxy-connection)\n- [Troubleshooting](#troubleshooting)\n- [Best Practices](#best-practices)\n\n## Basic Proxy Configuration\n\nUse the `--proxy` flag or set proxy via environment variable:\n\n```bash\n# Via CLI flag\nagent-browser --proxy \"http://proxy.example.com:8080\" open https://example.com\n\n# Via environment variable\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 using `--proxy-bypass` or `NO_PROXY`:\n\n```bash\n# Via CLI flag\nagent-browser --proxy \"http://proxy.example.com:8080\" --proxy-bypass \"localhost,*.internal.com\" open https://example.com\n\n# Via environment variable\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\nMultiple isolated browser sessions with state persistence and concurrent browsing.\n\n**Related**: [authentication.md](authentication.md) for login patterns, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [Named Sessions](#named-sessions)\n- [Session Isolation Properties](#session-isolation-properties)\n- [Session State Persistence](#session-state-persistence)\n- [Common Patterns](#common-patterns)\n- [Default Session](#default-session)\n- [Session Cleanup](#session-cleanup)\n- [Best Practices](#best-practices)\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\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 and Refs\n\nCompact element references that reduce context usage dramatically for AI agents.\n\n**Related**: [commands.md](commands.md) for full command reference, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [How Refs Work](#how-refs-work)\n- [Snapshot Command](#the-snapshot-command)\n- [Using Refs](#using-refs)\n- [Ref Lifecycle](#ref-lifecycle)\n- [Best Practices](#best-practices)\n- [Ref Notation Details](#ref-notation-details)\n- [Troubleshooting](#troubleshooting)\n\n## How Refs Work\n\nTraditional approach:\n\n```\nFull DOM/HTML → AI parses → CSS selector → Action (~3000-5000 tokens)\n```\n\nagent-browser approach:\n\n```\nCompact snapshot → @refs assigned → Direct interaction (~200-400 tokens)\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 down to reveal element\nagent-browser scroll down 1000\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":"# Video Recording\n\nCapture browser automation as video for debugging, documentation, or verification.\n\n**Related**: [commands.md](commands.md) for full command reference, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [Basic Recording](#basic-recording)\n- [Recording Commands](#recording-commands)\n- [Use Cases](#use-cases)\n- [Best Practices](#best-practices)\n- [Output Format](#output-format)\n- [Limitations](#limitations)\n\n## Basic Recording\n\n```bash\n# Start recording\nagent-browser record start ./demo.webm\n\n# Perform actions\nagent-browser open https://example.com\nagent-browser snapshot -i\nagent-browser click @e1\nagent-browser fill @e2 \"test input\"\n\n# Stop and save\nagent-browser record stop\n```\n\n## Recording Commands\n\n```bash\n# Start recording to file\nagent-browser record start ./output.webm\n\n# Stop current recording\nagent-browser record stop\n\n# Restart with new file (stops current + starts new)\nagent-browser record restart ./take2.webm\n```\n\n## Use Cases\n\n### Debugging Failed Automation\n\n```bash\n#!/bin/bash\n# Record automation for debugging\n\nagent-browser record start ./debug-$(date +%Y%m%d-%H%M%S).webm\n\n# Run your automation\nagent-browser open https://app.example.com\nagent-browser snapshot -i\nagent-browser click @e1 || {\n    echo \"Click failed - check recording\"\n    agent-browser record stop\n    exit 1\n}\n\nagent-browser record stop\n```\n\n### Documentation Generation\n\n```bash\n#!/bin/bash\n# Record workflow for documentation\n\nagent-browser record start ./docs/how-to-login.webm\n\nagent-browser open https://app.example.com/login\nagent-browser wait 1000  # Pause for visibility\n\nagent-browser snapshot -i\nagent-browser fill @e1 \"demo@example.com\"\nagent-browser wait 500\n\nagent-browser fill @e2 \"password\"\nagent-browser wait 500\n\nagent-browser click @e3\nagent-browser wait --load networkidle\nagent-browser wait 1000  # Show result\n\nagent-browser record stop\n```\n\n### CI/CD Test Evidence\n\n```bash\n#!/bin/bash\n# Record E2E test runs for CI artifacts\n\nTEST_NAME=\"${1:-e2e-test}\"\nRECORDING_DIR=\"./test-recordings\"\nmkdir -p \"$RECORDING_DIR\"\n\nagent-browser record start \"$RECORDING_DIR/$TEST_NAME-$(date +%s).webm\"\n\n# Run test\nif run_e2e_test; then\n    echo \"Test passed\"\nelse\n    echo \"Test failed - recording saved\"\nfi\n\nagent-browser record stop\n```\n\n## Best Practices\n\n### 1. Add Pauses for Clarity\n\n```bash\n# Slow down for human viewing\nagent-browser click @e1\nagent-browser wait 500  # Let viewer see result\n```\n\n### 2. Use Descriptive Filenames\n\n```bash\n# Include context in filename\nagent-browser record start ./recordings/login-flow-2024-01-15.webm\nagent-browser record start ./recordings/checkout-test-run-42.webm\n```\n\n### 3. Handle Recording in Error Cases\n\n```bash\n#!/bin/bash\nset -e\n\ncleanup() {\n    agent-browser record stop 2>/dev/null || true\n    agent-browser close 2>/dev/null || true\n}\ntrap cleanup EXIT\n\nagent-browser record start ./automation.webm\n# ... automation steps ...\n```\n\n### 4. Combine with Screenshots\n\n```bash\n# Record video AND capture key frames\nagent-browser record start ./flow.webm\n\nagent-browser open https://example.com\nagent-browser screenshot ./screenshots/step1-homepage.png\n\nagent-browser click @e1\nagent-browser screenshot ./screenshots/step2-after-click.png\n\nagent-browser record stop\n```\n\n## Output Format\n\n- Default format: WebM (VP8/VP9 codec)\n- Compatible with all modern browsers and video players\n- Compressed but high quality\n\n## Limitations\n\n- Recording adds slight overhead to automation\n- Large recordings can consume significant disk space\n- Some headless environments may have codec limitations\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 <pre key={`${message.id}-${i}`}>{JSON.stringify(part.toolInvocation, null, 2)}</pre>\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 (part.type === 'tool-getWeather' && (part.state === 'input-available' || part.state === 'output-available')) {\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 (part.type === 'tool-getWeather' && (part.state === 'input-available' || part.state === 'output-available')) {\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({ invocation }: { invocation: WeatherToolInvocation }) {\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/bibi/SKILL.md":"---\nname: bibi\ndescription: >\n  BibiGPT CLI for summarizing videos, audio, and podcasts directly in the terminal.\n  Use when the user wants to summarize a URL (YouTube, Bilibili, podcast, etc.)\n  or check their BibiGPT authentication status.\n  Requires the BibiGPT desktop app installed with an active login session,\n  or a BIBI_API_TOKEN environment variable set.\n---\n\n# BibiGPT CLI (`bibi`)\n\nSummarize videos, audio, and podcasts from the terminal using the BibiGPT API.\n\n## Installation\n\n### macOS (Homebrew)\n\n```bash\nbrew install --cask jimmylv/bibigpt/bibigpt\n```\n\n### Windows\n\nDownload the installer from: **https://bibigpt.co/download/desktop**\n\n> `winget install BibiGPT` is pending review and not yet available.\n\n### Verify installation\n\n```bash\nbibi --version\n```\n\n## Authentication\n\nAfter installing, the user must log in via the desktop app at least once. The CLI reads the saved session automatically from the desktop app's settings.\n\nAlternatively, set an API token:\n\n```bash\nexport BIBI_API_TOKEN=<token>\n```\n\nOn Windows (PowerShell):\n\n```powershell\n$env:BIBI_API_TOKEN=\"<token>\"\n```\n\n## Commands\n\n### Summarize a URL\n\n**Important**: URLs containing `?` or `&` must be quoted to avoid shell glob errors.\n\n```bash\n# Basic summary (Markdown output to stdout)\nbibi summarize \"<URL>\"\n\n# Async mode — recommended for long videos (>30min)\nbibi summarize \"<URL>\" --async\n\n# Chapter-by-chapter summary\nbibi summarize \"<URL>\" --chapter\n\n# Fetch subtitles/transcript only (no AI summary)\nbibi summarize \"<URL>\" --subtitle\n\n# Full JSON response\nbibi summarize \"<URL>\" --json\n\n# Combine flags\nbibi summarize \"<URL>\" --subtitle --json\n```\n\n**Supported URL types**: YouTube, Bilibili, podcasts, audio files, and any URL supported by BibiGPT.\n\n### Authentication\n\n```bash\n# Check current auth status\nbibi auth check\n\n# Open browser to log in\nbibi auth login\n\n# Show how to set API token\nbibi auth set-token <TOKEN>\n```\n\n### Updates\n\n```bash\n# Check if a new version is available\nbibi check-update\n\n# Download and install the latest version\nbibi self-update\n```\n\n## Output Format\n\n- **Default**: Markdown summary text sent to stdout. Progress info goes to stderr.\n- **--json**: Complete API response as pretty-printed JSON to stdout.\n\nThis means you can pipe the output:\n\n```bash\nbibi summarize \"<URL>\" > summary.md\nbibi summarize \"<URL>\" --json | jq '.summary'\n```\n\n## Error Handling\n\n| Exit Code | Meaning                                        |\n| --------- | ---------------------------------------------- |\n| 0         | Success                                        |\n| 1         | Error (auth missing, API error, timeout, etc.) |\n\n| HTTP Status | User Action                                                      |\n| ----------- | ---------------------------------------------------------------- |\n| 401         | Token expired — run `bibi auth login` or re-login in desktop app |\n| 402/403     | Quota exceeded — visit https://bibigpt.co/pricing                |\n| 429         | Rate limited — wait and retry                                    |\n\n## Usage Tips\n\n- For very long videos, use `--async` to avoid HTTP timeout.\n- Use `--subtitle` to get raw subtitles/transcript without AI summarization.\n- Use `--json` when you need structured data (e.g., `sourceUrl`, `htmlUrl`, `detail`).\n- The `--chapter` flag provides section-by-section summaries, useful for lectures or tutorials.\n- The CLI does NOT open any GUI window — all output goes to the terminal.\n- Run `bibi check-update` periodically to get new features and bug fixes.\n",".agents/skills/find-skills/SKILL.md":"---\nname: find-skills\ndescription: Helps users discover and install agent skills when they ask questions like \"how do I do X\", \"find a skill for X\", \"is there a skill that can...\", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.\n---\n\n# Find Skills\n\nThis skill helps you discover and install skills from the open agent skills ecosystem.\n\n## When to Use This Skill\n\nUse this skill when the user:\n\n- Asks \"how do I do X\" where X might be a common task with an existing skill\n- Says \"find a skill for X\" or \"is there a skill for X\"\n- Asks \"can you do X\" where X is a specialized capability\n- Expresses interest in extending agent capabilities\n- Wants to search for tools, templates, or workflows\n- Mentions they wish they had help with a specific domain (design, testing, deployment, etc.)\n\n## What is the Skills CLI?\n\nThe Skills CLI (`npx skills`) is the package manager for the open agent skills ecosystem. Skills are modular packages that extend agent capabilities with specialized knowledge, workflows, and tools.\n\n**Key commands:**\n\n- `npx skills find [query]` - Search for skills interactively or by keyword\n- `npx skills add <package>` - Install a skill from GitHub or other sources\n- `npx skills check` - Check for skill updates\n- `npx skills update` - Update all installed skills\n\n**Browse skills at:** https://skills.sh/\n\n## How to Help Users Find Skills\n\n### Step 1: Understand What They Need\n\nWhen a user asks for help with something, identify:\n\n1. The domain (e.g., React, testing, design, deployment)\n2. The specific task (e.g., writing tests, creating animations, reviewing PRs)\n3. Whether this is a common enough task that a skill likely exists\n\n### Step 2: Search for Skills\n\nRun the find command with a relevant query:\n\n```bash\nnpx skills find [query]\n```\n\nFor example:\n\n- User asks \"how do I make my React app faster?\" → `npx skills find react performance`\n- User asks \"can you help me with PR reviews?\" → `npx skills find pr review`\n- User asks \"I need to create a changelog\" → `npx skills find changelog`\n\nThe command will return results like:\n\n```\nInstall with npx skills add <owner/repo@skill>\n\nvercel-labs/agent-skills@vercel-react-best-practices\n└ https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices\n```\n\n### Step 3: Present Options to the User\n\nWhen you find relevant skills, present them to the user with:\n\n1. The skill name and what it does\n2. The install command they can run\n3. A link to learn more at skills.sh\n\nExample response:\n\n```\nI found a skill that might help! The \"vercel-react-best-practices\" skill provides\nReact and Next.js performance optimization guidelines from Vercel Engineering.\n\nTo install it:\nnpx skills add vercel-labs/agent-skills@vercel-react-best-practices\n\nLearn more: https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices\n```\n\n### Step 4: Offer to Install\n\nIf the user wants to proceed, you can install the skill for them:\n\n```bash\nnpx skills add <owner/repo@skill> -g -y\n```\n\nThe `-g` flag installs globally (user-level) and `-y` skips confirmation prompts.\n\n## Common Skill Categories\n\nWhen searching, consider these common categories:\n\n| Category        | Example Queries                          |\n| --------------- | ---------------------------------------- |\n| Web Development | react, nextjs, typescript, css, tailwind |\n| Testing         | testing, jest, playwright, e2e           |\n| DevOps          | deploy, docker, kubernetes, ci-cd        |\n| Documentation   | docs, readme, changelog, api-docs        |\n| Code Quality    | review, lint, refactor, best-practices   |\n| Design          | ui, ux, design-system, accessibility     |\n| Productivity    | workflow, automation, git                |\n\n## Tips for Effective Searches\n\n1. **Use specific keywords**: \"react testing\" is better than just \"testing\"\n2. **Try alternative terms**: If \"deploy\" doesn't work, try \"deployment\" or \"ci-cd\"\n3. **Check popular sources**: Many skills come from `vercel-labs/agent-skills` or `ComposioHQ/awesome-claude-skills`\n\n## When No Skills Are Found\n\nIf no relevant skills exist:\n\n1. Acknowledge that no existing skill was found\n2. Offer to help with the task directly using your general capabilities\n3. Suggest the user could create their own skill with `npx skills init`\n\nExample:\n\n```\nI searched for skills related to \"xyz\" but didn't find any matches.\nI can still help you with this task directly! Would you like me to proceed?\n\nIf this is something you do often, you could create your own skill:\nnpx skills init my-xyz-skill\n```\n",".agents/skills/next-best-practices/SKILL.md":"---\nname: next-best-practices\ndescription: Next.js best practices - file conventions, RSC boundaries, data patterns, async APIs, metadata, error handling, route handlers, image/font optimization, bundling\nuser-invocable: false\n---\n\n# Next.js Best Practices\n\nApply these rules when writing or reviewing Next.js code.\n\n## File Conventions\n\nSee [file-conventions.md](./file-conventions.md) for:\n\n- Project structure and special files\n- Route segments (dynamic, catch-all, groups)\n- Parallel and intercepting routes\n- Middleware rename in v16 (middleware → proxy)\n\n## RSC Boundaries\n\nDetect invalid React Server Component patterns.\n\nSee [rsc-boundaries.md](./rsc-boundaries.md) for:\n\n- Async client component detection (invalid)\n- Non-serializable props detection\n- Server Action exceptions\n\n## Async Patterns\n\nNext.js 15+ async API changes.\n\nSee [async-patterns.md](./async-patterns.md) for:\n\n- Async `params` and `searchParams`\n- Async `cookies()` and `headers()`\n- Migration codemod\n\n## Runtime Selection\n\nSee [runtime-selection.md](./runtime-selection.md) for:\n\n- Default to Node.js runtime\n- When Edge runtime is appropriate\n\n## Directives\n\nSee [directives.md](./directives.md) for:\n\n- `'use client'`, `'use server'` (React)\n- `'use cache'` (Next.js)\n\n## Functions\n\nSee [functions.md](./functions.md) for:\n\n- Navigation hooks: `useRouter`, `usePathname`, `useSearchParams`, `useParams`\n- Server functions: `cookies`, `headers`, `draftMode`, `after`\n- Generate functions: `generateStaticParams`, `generateMetadata`\n\n## Error Handling\n\nSee [error-handling.md](./error-handling.md) for:\n\n- `error.tsx`, `global-error.tsx`, `not-found.tsx`\n- `redirect`, `permanentRedirect`, `notFound`\n- `forbidden`, `unauthorized` (auth errors)\n- `unstable_rethrow` for catch blocks\n\n## Data Patterns\n\nSee [data-patterns.md](./data-patterns.md) for:\n\n- Server Components vs Server Actions vs Route Handlers\n- Avoiding data waterfalls (`Promise.all`, Suspense, preload)\n- Client component data fetching\n\n## Route Handlers\n\nSee [route-handlers.md](./route-handlers.md) for:\n\n- `route.ts` basics\n- GET handler conflicts with `page.tsx`\n- Environment behavior (no React DOM)\n- When to use vs Server Actions\n\n## Metadata & OG Images\n\nSee [metadata.md](./metadata.md) for:\n\n- Static and dynamic metadata\n- `generateMetadata` function\n- OG image generation with `next/og`\n- File-based metadata conventions\n\n## Image Optimization\n\nSee [image.md](./image.md) for:\n\n- Always use `next/image` over `<img>`\n- Remote images configuration\n- Responsive `sizes` attribute\n- Blur placeholders\n- Priority loading for LCP\n\n## Font Optimization\n\nSee [font.md](./font.md) for:\n\n- `next/font` setup\n- Google Fonts, local fonts\n- Tailwind CSS integration\n- Preloading subsets\n\n## Bundling\n\nSee [bundling.md](./bundling.md) for:\n\n- Server-incompatible packages\n- CSS imports (not link tags)\n- Polyfills (already included)\n- ESM/CommonJS issues\n- Bundle analysis\n\n## Scripts\n\nSee [scripts.md](./scripts.md) for:\n\n- `next/script` vs native script tags\n- Inline scripts need `id`\n- Loading strategies\n- Google Analytics with `@next/third-parties`\n\n## Hydration Errors\n\nSee [hydration-error.md](./hydration-error.md) for:\n\n- Common causes (browser APIs, dates, invalid HTML)\n- Debugging with error overlay\n- Fixes for each cause\n\n## Suspense Boundaries\n\nSee [suspense-boundaries.md](./suspense-boundaries.md) for:\n\n- CSR bailout with `useSearchParams` and `usePathname`\n- Which hooks require Suspense boundaries\n\n## Parallel & Intercepting Routes\n\nSee [parallel-routes.md](./parallel-routes.md) for:\n\n- Modal patterns with `@slot` and `(.)` interceptors\n- `default.tsx` for fallbacks\n- Closing modals correctly with `router.back()`\n\n## Self-Hosting\n\nSee [self-hosting.md](./self-hosting.md) for:\n\n- `output: 'standalone'` for Docker\n- Cache handlers for multi-instance ISR\n- What works vs needs extra setup\n\n## Debug Tricks\n\nSee [debug-tricks.md](./debug-tricks.md) for:\n\n- MCP endpoint for AI-assisted debugging\n- Rebuild specific routes with `--debug-build-paths`\n",".agents/skills/next-best-practices/async-patterns.md":"# Async Patterns\n\nIn Next.js 15+, `params`, `searchParams`, `cookies()`, and `headers()` are asynchronous.\n\n## Async Params and SearchParams\n\nAlways type them as `Promise<...>` and await them.\n\n### Pages and Layouts\n\n```tsx\ntype Props = { params: Promise<{ slug: string }> }\n\nexport default async function Page({ params }: Props) {\n  const { slug } = await params\n}\n```\n\n### Route Handlers\n\n```tsx\nexport async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n}\n```\n\n### SearchParams\n\n```tsx\ntype Props = {\n  params: Promise<{ slug: string }>\n  searchParams: Promise<{ query?: string }>\n}\n\nexport default async function Page({ params, searchParams }: Props) {\n  const { slug } = await params\n  const { query } = await searchParams\n}\n```\n\n### Synchronous Components\n\nUse `React.use()` for non-async components:\n\n```tsx\nimport { use } from 'react'\n\ntype Props = { params: Promise<{ slug: string }> }\n\nexport default function Page({ params }: Props) {\n  const { slug } = use(params)\n}\n```\n\n### generateMetadata\n\n```tsx\ntype Props = { params: Promise<{ slug: string }> }\n\nexport async function generateMetadata({ params }: Props): Promise<Metadata> {\n  const { slug } = await params\n  return { title: slug }\n}\n```\n\n## Async Cookies and Headers\n\n```tsx\nimport { cookies, headers } from 'next/headers'\n\nexport default async function Page() {\n  const cookieStore = await cookies()\n  const headersList = await headers()\n\n  const theme = cookieStore.get('theme')\n  const userAgent = headersList.get('user-agent')\n}\n```\n\n## Migration Codemod\n\n```bash\nnpx @next/codemod@latest next-async-request-api .\n```\n",".agents/skills/next-best-practices/bundling.md":"# Bundling\n\nFix common bundling issues with third-party packages.\n\n## Server-Incompatible Packages\n\nSome packages use browser APIs (`window`, `document`, `localStorage`) and fail in Server Components.\n\n### Error Signs\n\n```\nReferenceError: window is not defined\nReferenceError: document is not defined\nReferenceError: localStorage is not defined\nModule not found: Can't resolve 'fs'\n```\n\n### Solution 1: Mark as Client-Only\n\nIf the package is only needed on client:\n\n```tsx\n// Bad: Fails - package uses window\nimport SomeChart from 'some-chart-library'\n\nexport default function Page() {\n  return <SomeChart />\n}\n\n// Good: Use dynamic import with ssr: false\nimport dynamic from 'next/dynamic'\n\nconst SomeChart = dynamic(() => import('some-chart-library'), {\n  ssr: false,\n})\n\nexport default function Page() {\n  return <SomeChart />\n}\n```\n\n### Solution 2: Externalize from Server Bundle\n\nFor packages that should run on server but have bundling issues:\n\n```js\n// next.config.js\nmodule.exports = {\n  serverExternalPackages: ['problematic-package'],\n}\n```\n\nUse this for:\n\n- Packages with native bindings (sharp, bcrypt)\n- Packages that don't bundle well (some ORMs)\n- Packages with circular dependencies\n\n### Solution 3: Client Component Wrapper\n\nWrap the entire usage in a client component:\n\n```tsx\n// components/ChartWrapper.tsx\n'use client'\n\nimport { Chart } from 'chart-library'\n\nexport function ChartWrapper(props) {\n  return <Chart {...props} />\n}\n\n// app/page.tsx (server component)\nimport { ChartWrapper } from '@/components/ChartWrapper'\n\nexport default function Page() {\n  return <ChartWrapper data={data} />\n}\n```\n\n## CSS Imports\n\nImport CSS files instead of using `<link>` tags. Next.js handles bundling and optimization.\n\n```tsx\n// Bad: Manual link tag\n;<link rel=\"stylesheet\" href=\"/styles.css\" />\n\n// Good: Import CSS\nimport './styles.css'\n\n// Good: CSS Modules\nimport styles from './Button.module.css'\n```\n\n## Polyfills\n\nNext.js includes common polyfills automatically. Don't load redundant ones from polyfill.io or similar CDNs.\n\nAlready included: `Array.from`, `Object.assign`, `Promise`, `fetch`, `Map`, `Set`, `Symbol`, `URLSearchParams`, and 50+ others.\n\n```tsx\n// Bad: Redundant polyfills\n<script src=\"https://polyfill.io/v3/polyfill.min.js?features=fetch,Promise,Array.from\" />\n\n// Good: Next.js includes these automatically\n```\n\n## ESM/CommonJS Issues\n\n### Error Signs\n\n```\nSyntaxError: Cannot use import statement outside a module\nError: require() of ES Module\nModule not found: ESM packages need to be imported\n```\n\n### Solution: Transpile Package\n\n```js\n// next.config.js\nmodule.exports = {\n  transpilePackages: ['some-esm-package', 'another-package'],\n}\n```\n\n## Common Problematic Packages\n\n| Package         | Issue           | Solution                                                        |\n| --------------- | --------------- | --------------------------------------------------------------- |\n| `sharp`         | Native bindings | `serverExternalPackages: ['sharp']`                             |\n| `bcrypt`        | Native bindings | `serverExternalPackages: ['bcrypt']` or use `bcryptjs`          |\n| `canvas`        | Native bindings | `serverExternalPackages: ['canvas']`                            |\n| `recharts`      | Uses window     | `dynamic(() => import('recharts'), { ssr: false })`             |\n| `react-quill`   | Uses document   | `dynamic(() => import('react-quill'), { ssr: false })`          |\n| `mapbox-gl`     | Uses window     | `dynamic(() => import('mapbox-gl'), { ssr: false })`            |\n| `monaco-editor` | Uses window     | `dynamic(() => import('@monaco-editor/react'), { ssr: false })` |\n| `lottie-web`    | Uses document   | `dynamic(() => import('lottie-react'), { ssr: false })`         |\n\n## Bundle Analysis\n\nAnalyze bundle size with the built-in analyzer (Next.js 16.1+):\n\n```bash\nnext experimental-analyze\n```\n\nThis opens an interactive UI to:\n\n- Filter by route, environment (client/server), and type\n- Inspect module sizes and import chains\n- View treemap visualization\n\nSave output for comparison:\n\n```bash\nnext experimental-analyze --output\n# Output saved to .next/diagnostics/analyze\n```\n\nReference: https://nextjs.org/docs/app/guides/package-bundling\n\n## Migrating from Webpack to Turbopack\n\nTurbopack is the default bundler in Next.js 15+. If you have custom webpack config, migrate to Turbopack-compatible alternatives:\n\n```js\n// next.config.js\nmodule.exports = {\n  // Good: Works with Turbopack\n  serverExternalPackages: ['package'],\n  transpilePackages: ['package'],\n\n  // Bad: Webpack-only - migrate away from this\n  webpack: (config) => {\n    // custom webpack config\n  },\n}\n```\n\nReference: https://nextjs.org/docs/app/building-your-application/upgrading/from-webpack-to-turbopack\n",".agents/skills/next-best-practices/data-patterns.md":"# Data Patterns\n\nChoose the right data fetching pattern for each use case.\n\n## Decision Tree\n\n```\nNeed to fetch data?\n├── From a Server Component?\n│   └── Use: Fetch directly (no API needed)\n│\n├── From a Client Component?\n│   ├── Is it a mutation (POST/PUT/DELETE)?\n│   │   └── Use: Server Action\n│   └── Is it a read (GET)?\n│       └── Use: Route Handler OR pass from Server Component\n│\n├── Need external API access (webhooks, third parties)?\n│   └── Use: Route Handler\n│\n└── Need REST API for mobile app / external clients?\n    └── Use: Route Handler\n```\n\n## Pattern 1: Server Components (Preferred for Reads)\n\nFetch data directly in Server Components - no API layer needed.\n\n```tsx\n// app/users/page.tsx\nasync function UsersPage() {\n  // Direct database access - no API round-trip\n  const users = await db.user.findMany()\n\n  // Or fetch from external API\n  const posts = await fetch('https://api.example.com/posts').then((r) => r.json())\n\n  return (\n    <ul>\n      {users.map((user) => (\n        <li key={user.id}>{user.name}</li>\n      ))}\n    </ul>\n  )\n}\n```\n\n**Benefits**:\n\n- No API to maintain\n- No client-server waterfall\n- Secrets stay on server\n- Direct database access\n\n## Pattern 2: Server Actions (Preferred for Mutations)\n\nServer Actions are the recommended way to handle mutations.\n\n```tsx\n// app/actions.ts\n'use server'\n\nimport { revalidatePath } from 'next/cache'\n\nexport async function createPost(formData: FormData) {\n  const title = formData.get('title') as string\n\n  await db.post.create({ data: { title } })\n\n  revalidatePath('/posts')\n}\n\nexport async function deletePost(id: string) {\n  await db.post.delete({ where: { id } })\n\n  revalidateTag('posts')\n}\n```\n\n```tsx\n// app/posts/new/page.tsx\nimport { createPost } from '@/app/actions'\n\nexport default function NewPost() {\n  return (\n    <form action={createPost}>\n      <input name=\"title\" required />\n      <button type=\"submit\">Create</button>\n    </form>\n  )\n}\n```\n\n**Benefits**:\n\n- End-to-end type safety\n- Progressive enhancement (works without JS)\n- Automatic request handling\n- Integrated with React transitions\n\n**Constraints**:\n\n- POST only (no GET caching semantics)\n- Internal use only (no external access)\n- Cannot return non-serializable data\n\n## Pattern 3: Route Handlers (APIs)\n\nUse Route Handlers when you need a REST API.\n\n```tsx\n// app/api/posts/route.ts\nimport { NextRequest, NextResponse } from 'next/server'\n\n// GET is cacheable\nexport async function GET(request: NextRequest) {\n  const posts = await db.post.findMany()\n  return NextResponse.json(posts)\n}\n\n// POST for mutations\nexport async function POST(request: NextRequest) {\n  const body = await request.json()\n  const post = await db.post.create({ data: body })\n  return NextResponse.json(post, { status: 201 })\n}\n```\n\n**When to use**:\n\n- External API access (mobile apps, third parties)\n- Webhooks from external services\n- GET endpoints that need HTTP caching\n- OpenAPI/Swagger documentation needed\n\n**When NOT to use**:\n\n- Internal data fetching (use Server Components)\n- Mutations from your UI (use Server Actions)\n\n## Avoiding Data Waterfalls\n\n### Problem: Sequential Fetches\n\n```tsx\n// Bad: Sequential waterfalls\nasync function Dashboard() {\n  const user = await getUser() // Wait...\n  const posts = await getPosts() // Then wait...\n  const comments = await getComments() // Then wait...\n\n  return <div>...</div>\n}\n```\n\n### Solution 1: Parallel Fetching with Promise.all\n\n```tsx\n// Good: Parallel fetching\nasync function Dashboard() {\n  const [user, posts, comments] = await Promise.all([getUser(), getPosts(), getComments()])\n\n  return <div>...</div>\n}\n```\n\n### Solution 2: Streaming with Suspense\n\n```tsx\n// Good: Show content progressively\nimport { Suspense } from 'react'\n\nasync function Dashboard() {\n  return (\n    <div>\n      <Suspense fallback={<UserSkeleton />}>\n        <UserSection />\n      </Suspense>\n      <Suspense fallback={<PostsSkeleton />}>\n        <PostsSection />\n      </Suspense>\n    </div>\n  )\n}\n\nasync function UserSection() {\n  const user = await getUser() // Fetches independently\n  return <div>{user.name}</div>\n}\n\nasync function PostsSection() {\n  const posts = await getPosts() // Fetches independently\n  return <PostList posts={posts} />\n}\n```\n\n### Solution 3: Preload Pattern\n\n```tsx\n// lib/data.ts\nimport { cache } from 'react'\n\nexport const getUser = cache(async (id: string) => {\n  return db.user.findUnique({ where: { id } })\n})\n\nexport const preloadUser = (id: string) => {\n  void getUser(id) // Fire and forget\n}\n```\n\n```tsx\n// app/user/[id]/page.tsx\nimport { getUser, preloadUser } from '@/lib/data'\n\nexport default async function UserPage({ params }) {\n  const { id } = await params\n\n  // Start fetching early\n  preloadUser(id)\n\n  // Do other work...\n\n  // Data likely ready by now\n  const user = await getUser(id)\n  return <div>{user.name}</div>\n}\n```\n\n## Client Component Data Fetching\n\nWhen Client Components need data:\n\n### Option 1: Pass from Server Component (Preferred)\n\n```tsx\n// Server Component\nasync function Page() {\n  const data = await fetchData()\n  return <ClientComponent initialData={data} />\n}\n\n// Client Component\n;('use client')\nfunction ClientComponent({ initialData }) {\n  const [data, setData] = useState(initialData)\n  // ...\n}\n```\n\n### Option 2: Fetch on Mount (When Necessary)\n\n```tsx\n'use client'\nimport { useEffect, useState } from 'react'\n\nfunction ClientComponent() {\n  const [data, setData] = useState(null)\n\n  useEffect(() => {\n    fetch('/api/data')\n      .then((r) => r.json())\n      .then(setData)\n  }, [])\n\n  if (!data) return <Loading />\n  return <div>{data.value}</div>\n}\n```\n\n### Option 3: Server Action for Reads (Works But Not Ideal)\n\nServer Actions can be called from Client Components for reads, but this is not their intended purpose:\n\n```tsx\n'use client'\nimport { getData } from './actions'\nimport { useEffect, useState } from 'react'\n\nfunction ClientComponent() {\n  const [data, setData] = useState(null)\n\n  useEffect(() => {\n    getData().then(setData)\n  }, [])\n\n  return <div>{data?.value}</div>\n}\n```\n\n**Note**: Server Actions always use POST, so no HTTP caching. Prefer Route Handlers for cacheable reads.\n\n## Quick Reference\n\n| Pattern                | Use Case                    | HTTP Method | Caching              |\n| ---------------------- | --------------------------- | ----------- | -------------------- |\n| Server Component fetch | Internal reads              | Any         | Full Next.js caching |\n| Server Action          | Mutations, form submissions | POST only   | No                   |\n| Route Handler          | External APIs, webhooks     | Any         | GET can be cached    |\n| Client fetch to API    | Client-side reads           | Any         | HTTP cache headers   |\n",".agents/skills/next-best-practices/debug-tricks.md":"# Debug Tricks\n\nTricks to speed up debugging Next.js applications.\n\n## MCP Endpoint (Dev Server)\n\nNext.js exposes a `/_next/mcp` endpoint in development for AI-assisted debugging via MCP (Model Context Protocol).\n\n- **Next.js 16+**: Enabled by default, use `next-devtools-mcp`\n- **Next.js < 16**: Requires `experimental.mcpServer: true` in next.config.js\n\nReference: https://nextjs.org/docs/app/guides/mcp\n\n**Important**: Find the actual port of the running Next.js dev server (check terminal output or `package.json` scripts). Don't assume port 3000.\n\n### Request Format\n\nThe endpoint uses JSON-RPC 2.0 over HTTP POST:\n\n```bash\ncurl -X POST http://localhost:<port>/_next/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json, text/event-stream\" \\\n  -d '{\n    \"jsonrpc\": \"2.0\",\n    \"id\": \"1\",\n    \"method\": \"tools/call\",\n    \"params\": {\n      \"name\": \"<tool-name>\",\n      \"arguments\": {}\n    }\n  }'\n```\n\n### Available Tools\n\n#### `get_errors`\n\nGet current errors from dev server (build errors, runtime errors with source-mapped stacks):\n\n```json\n{ \"name\": \"get_errors\", \"arguments\": {} }\n```\n\n#### `get_routes`\n\nDiscover all routes by scanning filesystem:\n\n```json\n{ \"name\": \"get_routes\", \"arguments\": {} }\n// Optional: { \"name\": \"get_routes\", \"arguments\": { \"routerType\": \"app\" } }\n```\n\nReturns: `{ \"appRouter\": [\"/\", \"/api/users/[id]\", ...], \"pagesRouter\": [...] }`\n\n#### `get_project_metadata`\n\nGet project path and dev server URL:\n\n```json\n{ \"name\": \"get_project_metadata\", \"arguments\": {} }\n```\n\nReturns: `{ \"projectPath\": \"/path/to/project\", \"devServerUrl\": \"http://localhost:3000\" }`\n\n#### `get_page_metadata`\n\nGet runtime metadata about current page render (requires active browser session):\n\n```json\n{ \"name\": \"get_page_metadata\", \"arguments\": {} }\n```\n\nReturns segment trie data showing layouts, boundaries, and page components.\n\n#### `get_logs`\n\nGet path to Next.js development log file:\n\n```json\n{ \"name\": \"get_logs\", \"arguments\": {} }\n```\n\nReturns path to `<distDir>/logs/next-development.log`\n\n#### `get_server_action_by_id`\n\nLocate a Server Action by ID:\n\n```json\n{ \"name\": \"get_server_action_by_id\", \"arguments\": { \"actionId\": \"<action-id>\" } }\n```\n\n### Example: Get Errors\n\n```bash\ncurl -X POST http://localhost:<port>/_next/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json, text/event-stream\" \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"tools/call\",\"params\":{\"name\":\"get_errors\",\"arguments\":{}}}'\n```\n\n## Rebuild Specific Routes (Next.js 16+)\n\nUse `--debug-build-paths` to rebuild only specific routes instead of the entire app:\n\n```bash\n# Rebuild a specific route\nnext build --debug-build-paths \"/dashboard\"\n\n# Rebuild routes matching a glob\nnext build --debug-build-paths \"/api/*\"\n\n# Dynamic routes\nnext build --debug-build-paths \"/blog/[slug]\"\n```\n\nUse this to:\n\n- Quickly verify a build fix without full rebuild\n- Debug static generation issues for specific pages\n- Iterate faster on build errors\n",".agents/skills/next-best-practices/directives.md":"# Directives\n\n## React Directives\n\nThese are React directives, not Next.js specific.\n\n### `'use client'`\n\nMarks a component as a Client Component. Required for:\n\n- React hooks (`useState`, `useEffect`, etc.)\n- Event handlers (`onClick`, `onChange`)\n- Browser APIs (`window`, `localStorage`)\n\n```tsx\n'use client'\n\nimport { useState } from 'react'\n\nexport function Counter() {\n  const [count, setCount] = useState(0)\n  return <button onClick={() => setCount(count + 1)}>{count}</button>\n}\n```\n\nReference: https://react.dev/reference/rsc/use-client\n\n### `'use server'`\n\nMarks a function as a Server Action. Can be passed to Client Components.\n\n```tsx\n'use server'\n\nexport async function submitForm(formData: FormData) {\n  // Runs on server\n}\n```\n\nOr inline within a Server Component:\n\n```tsx\nexport default function Page() {\n  async function submit() {\n    'use server'\n    // Runs on server\n  }\n  return <form action={submit}>...</form>\n}\n```\n\nReference: https://react.dev/reference/rsc/use-server\n\n---\n\n## Next.js Directive\n\n### `'use cache'`\n\nMarks a function or component for caching. Part of Next.js Cache Components.\n\n```tsx\n'use cache'\n\nexport async function getCachedData() {\n  return await fetchData()\n}\n```\n\nRequires `cacheComponents: true` in `next.config.ts`.\n\nFor detailed usage including cache profiles, `cacheLife()`, `cacheTag()`, and `updateTag()`, see the `next-cache-components` skill.\n\nReference: https://nextjs.org/docs/app/api-reference/directives/use-cache\n",".agents/skills/next-best-practices/error-handling.md":"# Error Handling\n\nHandle errors gracefully in Next.js applications.\n\nReference: https://nextjs.org/docs/app/getting-started/error-handling\n\n## Error Boundaries\n\n### `error.tsx`\n\nCatches errors in a route segment and its children:\n\n```tsx\n'use client'\n\nexport default function Error({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {\n  return (\n    <div>\n      <h2>Something went wrong!</h2>\n      <button onClick={() => reset()}>Try again</button>\n    </div>\n  )\n}\n```\n\n**Important:** `error.tsx` must be a Client Component.\n\n### `global-error.tsx`\n\nCatches errors in root layout:\n\n```tsx\n'use client'\n\nexport default function GlobalError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {\n  return (\n    <html>\n      <body>\n        <h2>Something went wrong!</h2>\n        <button onClick={() => reset()}>Try again</button>\n      </body>\n    </html>\n  )\n}\n```\n\n**Important:** Must include `<html>` and `<body>` tags.\n\n## Server Actions: Navigation API Gotcha\n\n**Do NOT wrap navigation APIs in try-catch.** They throw special errors that Next.js handles internally.\n\nReference: https://nextjs.org/docs/app/api-reference/functions/redirect#behavior\n\n```tsx\n'use server'\n\nimport { redirect } from 'next/navigation'\nimport { notFound } from 'next/navigation'\n\n// Bad: try-catch catches the navigation \"error\"\nasync function createPost(formData: FormData) {\n  try {\n    const post = await db.post.create({ ... })\n    redirect(`/posts/${post.id}`)  // This throws!\n  } catch (error) {\n    // redirect() throw is caught here - navigation fails!\n    return { error: 'Failed to create post' }\n  }\n}\n\n// Good: Call navigation APIs outside try-catch\nasync function createPost(formData: FormData) {\n  let post\n  try {\n    post = await db.post.create({ ... })\n  } catch (error) {\n    return { error: 'Failed to create post' }\n  }\n  redirect(`/posts/${post.id}`)  // Outside try-catch\n}\n\n// Good: Re-throw navigation errors\nasync function createPost(formData: FormData) {\n  try {\n    const post = await db.post.create({ ... })\n    redirect(`/posts/${post.id}`)\n  } catch (error) {\n    if (error instanceof Error && error.message === 'NEXT_REDIRECT') {\n      throw error  // Re-throw navigation errors\n    }\n    return { error: 'Failed to create post' }\n  }\n}\n```\n\nSame applies to:\n\n- `redirect()` - 307 temporary redirect\n- `permanentRedirect()` - 308 permanent redirect\n- `notFound()` - 404 not found\n- `forbidden()` - 403 forbidden\n- `unauthorized()` - 401 unauthorized\n\nUse `unstable_rethrow()` to re-throw these errors in catch blocks:\n\n```tsx\nimport { unstable_rethrow } from 'next/navigation'\n\nasync function action() {\n  try {\n    // ...\n    redirect('/success')\n  } catch (error) {\n    unstable_rethrow(error) // Re-throws Next.js internal errors\n    return { error: 'Something went wrong' }\n  }\n}\n```\n\n## Redirects\n\n```tsx\nimport { redirect, permanentRedirect } from 'next/navigation'\n\n// 307 Temporary - use for most cases\nredirect('/new-path')\n\n// 308 Permanent - use for URL migrations (cached by browsers)\npermanentRedirect('/new-url')\n```\n\n## Auth Errors\n\nTrigger auth-related error pages:\n\n```tsx\nimport { forbidden, unauthorized } from 'next/navigation'\n\nasync function Page() {\n  const session = await getSession()\n\n  if (!session) {\n    unauthorized() // Renders unauthorized.tsx (401)\n  }\n\n  if (!session.hasAccess) {\n    forbidden() // Renders forbidden.tsx (403)\n  }\n\n  return <Dashboard />\n}\n```\n\nCreate corresponding error pages:\n\n```tsx\n// app/forbidden.tsx\nexport default function Forbidden() {\n  return <div>You don't have access to this resource</div>\n}\n\n// app/unauthorized.tsx\nexport default function Unauthorized() {\n  return <div>Please log in to continue</div>\n}\n```\n\n## Not Found\n\n### `not-found.tsx`\n\nCustom 404 page for a route segment:\n\n```tsx\nexport default function NotFound() {\n  return (\n    <div>\n      <h2>Not Found</h2>\n      <p>Could not find the requested resource</p>\n    </div>\n  )\n}\n```\n\n### Triggering Not Found\n\n```tsx\nimport { notFound } from 'next/navigation'\n\nexport default async function Page({ params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n  const post = await getPost(id)\n\n  if (!post) {\n    notFound() // Renders closest not-found.tsx\n  }\n\n  return <div>{post.title}</div>\n}\n```\n\n## Error Hierarchy\n\nErrors bubble up to the nearest error boundary:\n\n```\napp/\n├── error.tsx           # Catches errors from all children\n├── blog/\n│   ├── error.tsx       # Catches errors in /blog/*\n│   └── [slug]/\n│       ├── error.tsx   # Catches errors in /blog/[slug]\n│       └── page.tsx\n└── layout.tsx          # Errors here go to global-error.tsx\n```\n",".agents/skills/next-best-practices/file-conventions.md":"# File Conventions\n\nNext.js App Router uses file-based routing with special file conventions.\n\n## Project Structure\n\nReference: https://nextjs.org/docs/app/getting-started/project-structure\n\n```\napp/\n├── layout.tsx          # Root layout (required)\n├── page.tsx            # Home page (/)\n├── loading.tsx         # Loading UI\n├── error.tsx           # Error UI\n├── not-found.tsx       # 404 UI\n├── global-error.tsx    # Global error UI\n├── route.ts            # API endpoint\n├── template.tsx        # Re-rendered layout\n├── default.tsx         # Parallel route fallback\n├── blog/\n│   ├── page.tsx        # /blog\n│   └── [slug]/\n│       └── page.tsx    # /blog/:slug\n└── (group)/            # Route group (no URL impact)\n    └── page.tsx\n```\n\n## Special Files\n\n| File            | Purpose                                  |\n| --------------- | ---------------------------------------- |\n| `page.tsx`      | UI for a route segment                   |\n| `layout.tsx`    | Shared UI for segment and children       |\n| `loading.tsx`   | Loading UI (Suspense boundary)           |\n| `error.tsx`     | Error UI (Error boundary)                |\n| `not-found.tsx` | 404 UI                                   |\n| `route.ts`      | API endpoint                             |\n| `template.tsx`  | Like layout but re-renders on navigation |\n| `default.tsx`   | Fallback for parallel routes             |\n\n## Route Segments\n\n```\napp/\n├── blog/               # Static segment: /blog\n├── [slug]/             # Dynamic segment: /:slug\n├── [...slug]/          # Catch-all: /a/b/c\n├── [[...slug]]/        # Optional catch-all: / or /a/b/c\n└── (marketing)/        # Route group (ignored in URL)\n```\n\n## Parallel Routes\n\n```\napp/\n├── @analytics/\n│   └── page.tsx\n├── @sidebar/\n│   └── page.tsx\n└── layout.tsx          # Receives { analytics, sidebar } as props\n```\n\n## Intercepting Routes\n\n```\napp/\n├── feed/\n│   └── page.tsx\n├── @modal/\n│   └── (.)photo/[id]/  # Intercepts /photo/[id] from /feed\n│       └── page.tsx\n└── photo/[id]/\n    └── page.tsx\n```\n\nConventions:\n\n- `(.)` - same level\n- `(..)` - one level up\n- `(..)(..)` - two levels up\n- `(...)` - from root\n\n## Private Folders\n\n```\napp/\n├── _components/        # Private folder (not a route)\n│   └── Button.tsx\n└── page.tsx\n```\n\nPrefix with `_` to exclude from routing.\n\n## Middleware / Proxy\n\n### Next.js 14-15: `middleware.ts`\n\n```ts\n// middleware.ts (root of project)\nimport { NextResponse } from 'next/server'\nimport type { NextRequest } from 'next/server'\n\nexport function middleware(request: NextRequest) {\n  // Auth, redirects, rewrites, etc.\n  return NextResponse.next()\n}\n\nexport const config = {\n  matcher: ['/dashboard/:path*', '/api/:path*'],\n}\n```\n\n### Next.js 16+: `proxy.ts`\n\nRenamed for clarity - same capabilities, different names:\n\n```ts\n// proxy.ts (root of project)\nimport { NextResponse } from 'next/server'\nimport type { NextRequest } from 'next/server'\n\nexport function proxy(request: NextRequest) {\n  // Same logic as middleware\n  return NextResponse.next()\n}\n\nexport const proxyConfig = {\n  matcher: ['/dashboard/:path*', '/api/:path*'],\n}\n```\n\n| Version | File            | Export         | Config        |\n| ------- | --------------- | -------------- | ------------- |\n| v14-15  | `middleware.ts` | `middleware()` | `config`      |\n| v16+    | `proxy.ts`      | `proxy()`      | `proxyConfig` |\n\n**Migration**: Run `npx @next/codemod@latest upgrade` to auto-rename.\n\n## File Conventions Reference\n\nReference: https://nextjs.org/docs/app/api-reference/file-conventions\n",".agents/skills/next-best-practices/font.md":"# Font Optimization\n\nUse `next/font` for automatic font optimization with zero layout shift.\n\n## Google Fonts\n\n```tsx\n// app/layout.tsx\nimport { Inter } from 'next/font/google'\n\nconst inter = Inter({ subsets: ['latin'] })\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\" className={inter.className}>\n      <body>{children}</body>\n    </html>\n  )\n}\n```\n\n## Multiple Fonts\n\n```tsx\nimport { Inter, Roboto_Mono } from 'next/font/google'\n\nconst inter = Inter({\n  subsets: ['latin'],\n  variable: '--font-inter',\n})\n\nconst robotoMono = Roboto_Mono({\n  subsets: ['latin'],\n  variable: '--font-roboto-mono',\n})\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\" className={`${inter.variable} ${robotoMono.variable}`}>\n      <body>{children}</body>\n    </html>\n  )\n}\n```\n\nUse in CSS:\n\n```css\nbody {\n  font-family: var(--font-inter);\n}\n\ncode {\n  font-family: var(--font-roboto-mono);\n}\n```\n\n## Font Weights and Styles\n\n```tsx\n// Single weight\nconst inter = Inter({\n  subsets: ['latin'],\n  weight: '400',\n})\n\n// Multiple weights\nconst inter = Inter({\n  subsets: ['latin'],\n  weight: ['400', '500', '700'],\n})\n\n// Variable font (recommended) - includes all weights\nconst inter = Inter({\n  subsets: ['latin'],\n  // No weight needed - variable fonts support all weights\n})\n\n// With italic\nconst inter = Inter({\n  subsets: ['latin'],\n  style: ['normal', 'italic'],\n})\n```\n\n## Local Fonts\n\n```tsx\nimport localFont from 'next/font/local'\n\nconst myFont = localFont({\n  src: './fonts/MyFont.woff2',\n})\n\n// Multiple files for different weights\nconst myFont = localFont({\n  src: [\n    {\n      path: './fonts/MyFont-Regular.woff2',\n      weight: '400',\n      style: 'normal',\n    },\n    {\n      path: './fonts/MyFont-Bold.woff2',\n      weight: '700',\n      style: 'normal',\n    },\n  ],\n})\n\n// Variable font\nconst myFont = localFont({\n  src: './fonts/MyFont-Variable.woff2',\n  variable: '--font-my-font',\n})\n```\n\n## Tailwind CSS Integration\n\n```tsx\n// app/layout.tsx\nimport { Inter } from 'next/font/google'\n\nconst inter = Inter({\n  subsets: ['latin'],\n  variable: '--font-inter',\n})\n\nexport default function RootLayout({ children }) {\n  return (\n    <html lang=\"en\" className={inter.variable}>\n      <body>{children}</body>\n    </html>\n  )\n}\n```\n\n```js\n// tailwind.config.js\nmodule.exports = {\n  theme: {\n    extend: {\n      fontFamily: {\n        sans: ['var(--font-inter)'],\n      },\n    },\n  },\n}\n```\n\n## Preloading Subsets\n\nOnly load needed character subsets:\n\n```tsx\n// Latin only (most common)\nconst inter = Inter({ subsets: ['latin'] })\n\n// Multiple subsets\nconst inter = Inter({ subsets: ['latin', 'latin-ext', 'cyrillic'] })\n```\n\n## Display Strategy\n\nControl font loading behavior:\n\n```tsx\nconst inter = Inter({\n  subsets: ['latin'],\n  display: 'swap', // Default - shows fallback, swaps when loaded\n})\n\n// Options:\n// 'auto' - browser decides\n// 'block' - short block period, then swap\n// 'swap' - immediate fallback, swap when ready (recommended)\n// 'fallback' - short block, short swap, then fallback\n// 'optional' - short block, no swap (use if font is optional)\n```\n\n## Don't Use Manual Font Links\n\nAlways use `next/font` instead of `<link>` tags for Google Fonts.\n\n```tsx\n// Bad: Manual link tag (blocks rendering, no optimization)\n<link href=\"https://fonts.googleapis.com/css2?family=Inter\" rel=\"stylesheet\" />\n\n// Bad: Missing display and preconnect\n<link href=\"https://fonts.googleapis.com/css2?family=Inter\" rel=\"stylesheet\" />\n\n// Good: Use next/font (self-hosted, zero layout shift)\nimport { Inter } from 'next/font/google'\n\nconst inter = Inter({ subsets: ['latin'] })\n```\n\n## Common Mistakes\n\n```tsx\n// Bad: Importing font in every component\n// components/Button.tsx\nimport { Inter } from 'next/font/google'\nconst inter = Inter({ subsets: ['latin'] }) // Creates new instance each time!\n\n// Good: Import once in layout, use CSS variable\n// app/layout.tsx\nconst inter = Inter({ subsets: ['latin'], variable: '--font-inter' })\n\n// Bad: Using @import in CSS (blocks rendering)\n/* globals.css */\n@import url('https://fonts.googleapis.com/css2?family=Inter');\n\n// Good: Use next/font (self-hosted, no network request)\nimport { Inter } from 'next/font/google'\n\n// Bad: Loading all weights when only using a few\nconst inter = Inter({ subsets: ['latin'] }) // Loads all weights\n\n// Good: Specify only needed weights (for non-variable fonts)\nconst inter = Inter({ subsets: ['latin'], weight: ['400', '700'] })\n\n// Bad: Missing subset - loads all characters\nconst inter = Inter({})\n\n// Good: Always specify subset\nconst inter = Inter({ subsets: ['latin'] })\n```\n\n## Font in Specific Components\n\n```tsx\n// For component-specific fonts, export from a shared file\n// lib/fonts.ts\nimport { Inter, Playfair_Display } from 'next/font/google'\n\nexport const inter = Inter({ subsets: ['latin'], variable: '--font-inter' })\nexport const playfair = Playfair_Display({ subsets: ['latin'], variable: '--font-playfair' })\n\n// components/Heading.tsx\nimport { playfair } from '@/lib/fonts'\n\nexport function Heading({ children }) {\n  return <h1 className={playfair.className}>{children}</h1>\n}\n```\n",".agents/skills/next-best-practices/functions.md":"# Functions\n\nNext.js function APIs.\n\nReference: https://nextjs.org/docs/app/api-reference/functions\n\n## Navigation Hooks (Client)\n\n| Hook                        | Purpose                                                        | Reference                                                                                |\n| --------------------------- | -------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |\n| `useRouter`                 | Programmatic navigation (`push`, `replace`, `back`, `refresh`) | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-router)                   |\n| `usePathname`               | Get current pathname                                           | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-pathname)                 |\n| `useSearchParams`           | Read URL search parameters                                     | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-search-params)            |\n| `useParams`                 | Access dynamic route parameters                                | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-params)                   |\n| `useSelectedLayoutSegment`  | Active child segment (one level)                               | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-selected-layout-segment)  |\n| `useSelectedLayoutSegments` | All active segments below layout                               | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-selected-layout-segments) |\n| `useLinkStatus`             | Check link prefetch status                                     | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-link-status)              |\n| `useReportWebVitals`        | Report Core Web Vitals metrics                                 | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-report-web-vitals)        |\n\n## Server Functions\n\n| Function     | Purpose                                      | Reference                                                              |\n| ------------ | -------------------------------------------- | ---------------------------------------------------------------------- |\n| `cookies`    | Read/write cookies                           | [Docs](https://nextjs.org/docs/app/api-reference/functions/cookies)    |\n| `headers`    | Read request headers                         | [Docs](https://nextjs.org/docs/app/api-reference/functions/headers)    |\n| `draftMode`  | Enable preview of unpublished CMS content    | [Docs](https://nextjs.org/docs/app/api-reference/functions/draft-mode) |\n| `after`      | Run code after response finishes streaming   | [Docs](https://nextjs.org/docs/app/api-reference/functions/after)      |\n| `connection` | Wait for connection before dynamic rendering | [Docs](https://nextjs.org/docs/app/api-reference/functions/connection) |\n| `userAgent`  | Parse User-Agent header                      | [Docs](https://nextjs.org/docs/app/api-reference/functions/userAgent)  |\n\n## Generate Functions\n\n| Function                | Purpose                                 | Reference                                                                           |\n| ----------------------- | --------------------------------------- | ----------------------------------------------------------------------------------- |\n| `generateStaticParams`  | Pre-render dynamic routes at build time | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-static-params)  |\n| `generateMetadata`      | Dynamic metadata                        | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-metadata)       |\n| `generateViewport`      | Dynamic viewport config                 | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-viewport)       |\n| `generateSitemaps`      | Multiple sitemaps for large sites       | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-sitemaps)       |\n| `generateImageMetadata` | Multiple OG images per route            | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-image-metadata) |\n\n## Request/Response\n\n| Function        | Purpose                        | Reference                                                                  |\n| --------------- | ------------------------------ | -------------------------------------------------------------------------- |\n| `NextRequest`   | Extended Request with helpers  | [Docs](https://nextjs.org/docs/app/api-reference/functions/next-request)   |\n| `NextResponse`  | Extended Response with helpers | [Docs](https://nextjs.org/docs/app/api-reference/functions/next-response)  |\n| `ImageResponse` | Generate OG images             | [Docs](https://nextjs.org/docs/app/api-reference/functions/image-response) |\n\n## Common Examples\n\n### Navigation\n\nUse `next/link` for internal navigation instead of `<a>` tags.\n\n```tsx\n// Bad: Plain anchor tag\n;<a href=\"/about\">About</a>\n\n// Good: Next.js Link\nimport Link from 'next/link'\n;<Link href=\"/about\">About</Link>\n```\n\nActive link styling:\n\n```tsx\n'use client'\n\nimport Link from 'next/link'\nimport { usePathname } from 'next/navigation'\n\nexport function NavLink({ href, children }) {\n  const pathname = usePathname()\n\n  return (\n    <Link href={href} className={pathname === href ? 'active' : ''}>\n      {children}\n    </Link>\n  )\n}\n```\n\n### Static Generation\n\n```tsx\n// app/blog/[slug]/page.tsx\nexport async function generateStaticParams() {\n  const posts = await getPosts()\n  return posts.map((post) => ({ slug: post.slug }))\n}\n```\n\n### After Response\n\n```tsx\nimport { after } from 'next/server'\n\nexport async function POST(request: Request) {\n  const data = await processRequest(request)\n\n  after(async () => {\n    await logAnalytics(data)\n  })\n\n  return Response.json({ success: true })\n}\n```\n",".agents/skills/next-best-practices/hydration-error.md":"# Hydration Errors\n\nDiagnose and fix React hydration mismatch errors.\n\n## Error Signs\n\n- \"Hydration failed because the initial UI does not match\"\n- \"Text content does not match server-rendered HTML\"\n\n## Debugging\n\nIn development, click the hydration error to see the server/client diff.\n\n## Common Causes and Fixes\n\n### Browser-only APIs\n\n```tsx\n// Bad: Causes mismatch - window doesn't exist on server\n;<div>{window.innerWidth}</div>\n\n// Good: Use client component with mounted check\n;('use client')\nimport { useState, useEffect } from 'react'\n\nexport function ClientOnly({ children }: { children: React.ReactNode }) {\n  const [mounted, setMounted] = useState(false)\n  useEffect(() => setMounted(true), [])\n  return mounted ? children : null\n}\n```\n\n### Date/Time Rendering\n\nServer and client may be in different timezones:\n\n```tsx\n// Bad: Causes mismatch\n;<span>{new Date().toLocaleString()}</span>\n\n// Good: Render on client only\n;('use client')\nconst [time, setTime] = useState<string>()\nuseEffect(() => setTime(new Date().toLocaleString()), [])\n```\n\n### Random Values or IDs\n\n```tsx\n// Bad: Random values differ between server and client\n<div id={Math.random().toString()}>\n\n// Good: Use useId hook\nimport { useId } from 'react'\n\nfunction Input() {\n  const id = useId()\n  return <input id={id} />\n}\n```\n\n### Invalid HTML Nesting\n\n```tsx\n// Bad: Invalid - div inside p\n<p><div>Content</div></p>\n\n// Bad: Invalid - p inside p\n<p><p>Nested</p></p>\n\n// Good: Valid nesting\n<div><p>Content</p></div>\n```\n\n### Third-party Scripts\n\nScripts that modify DOM during hydration.\n\n```tsx\n// Good: Use next/script with afterInteractive\nimport Script from 'next/script'\n\nexport default function Page() {\n  return <Script src=\"https://example.com/script.js\" strategy=\"afterInteractive\" />\n}\n```\n",".agents/skills/next-best-practices/image.md":"# Image Optimization\n\nUse `next/image` for automatic image optimization.\n\n## Always Use next/image\n\n```tsx\n// Bad: Avoid native img\n;<img src=\"/hero.png\" alt=\"Hero\" />\n\n// Good: Use next/image\nimport Image from 'next/image'\n;<Image src=\"/hero.png\" alt=\"Hero\" width={800} height={400} />\n```\n\n## Required Props\n\nImages need explicit dimensions to prevent layout shift:\n\n```tsx\n// Local images - dimensions inferred automatically\nimport heroImage from './hero.png'\n<Image src={heroImage} alt=\"Hero\" />\n\n// Remote images - must specify width/height\n<Image src=\"https://example.com/image.jpg\" alt=\"Hero\" width={800} height={400} />\n\n// Or use fill for parent-relative sizing\n<div style={{ position: 'relative', width: '100%', height: 400 }}>\n  <Image src=\"/hero.png\" alt=\"Hero\" fill style={{ objectFit: 'cover' }} />\n</div>\n```\n\n## Remote Images Configuration\n\nRemote domains must be configured in `next.config.js`:\n\n```js\n// next.config.js\nmodule.exports = {\n  images: {\n    remotePatterns: [\n      {\n        protocol: 'https',\n        hostname: 'example.com',\n        pathname: '/images/**',\n      },\n      {\n        protocol: 'https',\n        hostname: '*.cdn.com', // Wildcard subdomain\n      },\n    ],\n  },\n}\n```\n\n## Responsive Images\n\nUse `sizes` to tell the browser which size to download:\n\n```tsx\n// Full-width hero\n<Image\n  src=\"/hero.png\"\n  alt=\"Hero\"\n  fill\n  sizes=\"100vw\"\n/>\n\n// Responsive grid (3 columns on desktop, 1 on mobile)\n<Image\n  src=\"/card.png\"\n  alt=\"Card\"\n  fill\n  sizes=\"(max-width: 768px) 100vw, 33vw\"\n/>\n\n// Fixed sidebar image\n<Image\n  src=\"/avatar.png\"\n  alt=\"Avatar\"\n  width={200}\n  height={200}\n  sizes=\"200px\"\n/>\n```\n\n## Blur Placeholder\n\nPrevent layout shift with placeholders:\n\n```tsx\n// Local images - automatic blur hash\nimport heroImage from './hero.png'\n<Image src={heroImage} alt=\"Hero\" placeholder=\"blur\" />\n\n// Remote images - provide blurDataURL\n<Image\n  src=\"https://example.com/image.jpg\"\n  alt=\"Hero\"\n  width={800}\n  height={400}\n  placeholder=\"blur\"\n  blurDataURL=\"data:image/jpeg;base64,/9j/4AAQSkZJRg...\"\n/>\n\n// Or use color placeholder\n<Image\n  src=\"https://example.com/image.jpg\"\n  alt=\"Hero\"\n  width={800}\n  height={400}\n  placeholder=\"empty\"\n  style={{ backgroundColor: '#e0e0e0' }}\n/>\n```\n\n## Priority Loading\n\nUse `priority` for above-the-fold images (LCP):\n\n```tsx\n// Hero image - loads immediately\n<Image src=\"/hero.png\" alt=\"Hero\" fill priority />\n\n// Below-fold images - lazy loaded by default (no priority needed)\n<Image src=\"/card.png\" alt=\"Card\" width={400} height={300} />\n```\n\n## Common Mistakes\n\n```tsx\n// Bad: Missing sizes with fill - downloads largest image\n<Image src=\"/hero.png\" alt=\"Hero\" fill />\n\n// Good: Add sizes for proper responsive behavior\n<Image src=\"/hero.png\" alt=\"Hero\" fill sizes=\"100vw\" />\n\n// Bad: Using width/height for aspect ratio only\n<Image src=\"/hero.png\" alt=\"Hero\" width={16} height={9} />\n\n// Good: Use actual display dimensions or fill with sizes\n<Image src=\"/hero.png\" alt=\"Hero\" fill sizes=\"100vw\" style={{ objectFit: 'cover' }} />\n\n// Bad: Remote image without config\n<Image src=\"https://untrusted.com/image.jpg\" alt=\"Image\" width={400} height={300} />\n// Error: Invalid src prop, hostname not configured\n\n// Good: Add hostname to next.config.js remotePatterns\n```\n\n## Static Export\n\nWhen using `output: 'export'`, use `unoptimized` or custom loader:\n\n```tsx\n// Option 1: Disable optimization\n;<Image src=\"/hero.png\" alt=\"Hero\" width={800} height={400} unoptimized />\n\n// Option 2: Global config\n// next.config.js\nmodule.exports = {\n  output: 'export',\n  images: { unoptimized: true },\n}\n\n// Option 3: Custom loader (Cloudinary, Imgix, etc.)\nconst cloudinaryLoader = ({ src, width, quality }) => {\n  return `https://res.cloudinary.com/demo/image/upload/w_${width},q_${quality || 75}/${src}`\n}\n\n;<Image loader={cloudinaryLoader} src=\"sample.jpg\" alt=\"Sample\" width={800} height={400} />\n```\n",".agents/skills/next-best-practices/metadata.md":"# Metadata\n\nAdd SEO metadata to Next.js pages using the Metadata API.\n\n## Important: Server Components Only\n\nThe `metadata` object and `generateMetadata` function are **only supported in Server Components**. They cannot be used in Client Components.\n\nIf the target page has `'use client'`:\n\n1. Remove `'use client'` if possible, move client logic to child components\n2. Or extract metadata to a parent Server Component layout\n3. Or split the file: Server Component with metadata imports Client Components\n\n## Static Metadata\n\n```tsx\nimport type { Metadata } from 'next'\n\nexport const metadata: Metadata = {\n  title: 'Page Title',\n  description: 'Page description for search engines',\n}\n```\n\n## Dynamic Metadata\n\n```tsx\nimport type { Metadata } from 'next'\n\ntype Props = { params: Promise<{ slug: string }> }\n\nexport async function generateMetadata({ params }: Props): Promise<Metadata> {\n  const { slug } = await params\n  const post = await getPost(slug)\n  return { title: post.title, description: post.description }\n}\n```\n\n## Avoid Duplicate Fetches\n\nUse React `cache()` when the same data is needed for both metadata and page:\n\n```tsx\nimport { cache } from 'react'\n\nexport const getPost = cache(async (slug: string) => {\n  return await db.posts.findFirst({ where: { slug } })\n})\n```\n\n## Viewport\n\nSeparate from metadata for streaming support:\n\n```tsx\nimport type { Viewport } from 'next'\n\nexport const viewport: Viewport = {\n  width: 'device-width',\n  initialScale: 1,\n  themeColor: '#000000',\n}\n\n// Or dynamic\nexport function generateViewport({ params }): Viewport {\n  return { themeColor: getThemeColor(params) }\n}\n```\n\n## Title Templates\n\nIn root layout for consistent naming:\n\n```tsx\nexport const metadata: Metadata = {\n  title: { default: 'Site Name', template: '%s | Site Name' },\n}\n```\n\n## Metadata File Conventions\n\nReference: https://nextjs.org/docs/app/getting-started/project-structure#metadata-file-conventions\n\nPlace these files in `app/` directory (or route segments):\n\n| File                            | Purpose                                       |\n| ------------------------------- | --------------------------------------------- |\n| `favicon.ico`                   | Favicon                                       |\n| `icon.png` / `icon.svg`         | App icon                                      |\n| `apple-icon.png`                | Apple app icon                                |\n| `opengraph-image.png`           | OG image                                      |\n| `twitter-image.png`             | Twitter card image                            |\n| `sitemap.ts` / `sitemap.xml`    | Sitemap (use `generateSitemaps` for multiple) |\n| `robots.ts` / `robots.txt`      | Robots directives                             |\n| `manifest.ts` / `manifest.json` | Web app manifest                              |\n\n## SEO Best Practice: Static Files Are Often Enough\n\nFor most sites, **static metadata files provide excellent SEO coverage**:\n\n```\napp/\n├── favicon.ico\n├── opengraph-image.png     # Works for both OG and Twitter\n├── sitemap.ts\n├── robots.ts\n└── layout.tsx              # With title/description metadata\n```\n\n**Tips:**\n\n- A single `opengraph-image.png` covers both Open Graph and Twitter (Twitter falls back to OG)\n- Static `title` and `description` in layout metadata is sufficient for most pages\n- Only use dynamic `generateMetadata` when content varies per page\n\n---\n\n# OG Image Generation\n\nGenerate dynamic Open Graph images using `next/og`.\n\n## Important Rules\n\n1. **Use `next/og`** - not `@vercel/og` (it's built into Next.js)\n2. **No searchParams** - OG images can't access search params, use route params instead\n3. **Avoid Edge runtime** - Use default Node.js runtime\n\n```tsx\n// Good\nimport { ImageResponse } from 'next/og'\n\n// Bad\n// import { ImageResponse } from '@vercel/og'\n// export const runtime = 'edge'\n```\n\n## Basic OG Image\n\n```tsx\n// app/opengraph-image.tsx\nimport { ImageResponse } from 'next/og'\n\nexport const alt = 'Site Name'\nexport const size = { width: 1200, height: 630 }\nexport const contentType = 'image/png'\n\nexport default function Image() {\n  return new ImageResponse(\n    (\n      <div\n        style={{\n          fontSize: 128,\n          background: 'white',\n          width: '100%',\n          height: '100%',\n          display: 'flex',\n          alignItems: 'center',\n          justifyContent: 'center',\n        }}\n      >\n        Hello World\n      </div>\n    ),\n    { ...size },\n  )\n}\n```\n\n## Dynamic OG Image\n\n```tsx\n// app/blog/[slug]/opengraph-image.tsx\nimport { ImageResponse } from 'next/og'\n\nexport const alt = 'Blog Post'\nexport const size = { width: 1200, height: 630 }\nexport const contentType = 'image/png'\n\ntype Props = { params: Promise<{ slug: string }> }\n\nexport default async function Image({ params }: Props) {\n  const { slug } = await params\n  const post = await getPost(slug)\n\n  return new ImageResponse(\n    (\n      <div\n        style={{\n          fontSize: 48,\n          background: 'linear-gradient(to bottom, #1a1a1a, #333)',\n          color: 'white',\n          width: '100%',\n          height: '100%',\n          display: 'flex',\n          flexDirection: 'column',\n          alignItems: 'center',\n          justifyContent: 'center',\n          padding: 48,\n        }}\n      >\n        <div style={{ fontSize: 64, fontWeight: 'bold' }}>{post.title}</div>\n        <div style={{ marginTop: 24, opacity: 0.8 }}>{post.description}</div>\n      </div>\n    ),\n    { ...size },\n  )\n}\n```\n\n## Custom Fonts\n\n```tsx\nimport { ImageResponse } from 'next/og'\nimport { join } from 'path'\nimport { readFile } from 'fs/promises'\n\nexport default async function Image() {\n  const fontPath = join(process.cwd(), 'assets/fonts/Inter-Bold.ttf')\n  const fontData = await readFile(fontPath)\n\n  return new ImageResponse(<div style={{ fontFamily: 'Inter', fontSize: 64 }}>Custom Font Text</div>, {\n    width: 1200,\n    height: 630,\n    fonts: [{ name: 'Inter', data: fontData, style: 'normal' }],\n  })\n}\n```\n\n## File Naming\n\n- `opengraph-image.tsx` - Open Graph (Facebook, LinkedIn)\n- `twitter-image.tsx` - Twitter/X cards (optional, falls back to OG)\n\n## Styling Notes\n\nImageResponse uses Flexbox layout:\n\n- Use `display: 'flex'`\n- No CSS Grid support\n- Styles must be inline objects\n\n## Multiple OG Images\n\nUse `generateImageMetadata` for multiple images per route:\n\n```tsx\n// app/blog/[slug]/opengraph-image.tsx\nimport { ImageResponse } from 'next/og'\n\nexport async function generateImageMetadata({ params }) {\n  const images = await getPostImages(params.slug)\n  return images.map((img, idx) => ({\n    id: idx,\n    alt: img.alt,\n    size: { width: 1200, height: 630 },\n    contentType: 'image/png',\n  }))\n}\n\nexport default async function Image({ params, id }) {\n  const images = await getPostImages(params.slug)\n  const image = images[id]\n  return new ImageResponse(/* ... */)\n}\n```\n\n## Multiple Sitemaps\n\nUse `generateSitemaps` for large sites:\n\n```tsx\n// app/sitemap.ts\nimport type { MetadataRoute } from 'next'\n\nexport async function generateSitemaps() {\n  // Return array of sitemap IDs\n  return [{ id: 0 }, { id: 1 }, { id: 2 }]\n}\n\nexport default async function sitemap({ id }: { id: number }): Promise<MetadataRoute.Sitemap> {\n  const start = id * 50000\n  const end = start + 50000\n  const products = await getProducts(start, end)\n\n  return products.map((product) => ({\n    url: `https://example.com/product/${product.id}`,\n    lastModified: product.updatedAt,\n  }))\n}\n```\n\nGenerates `/sitemap/0.xml`, `/sitemap/1.xml`, etc.\n",".agents/skills/next-best-practices/parallel-routes.md":"# Parallel & Intercepting Routes\n\nParallel routes render multiple pages in the same layout. Intercepting routes show a different UI when navigating from within your app vs direct URL access. Together they enable modal patterns.\n\n## File Structure\n\n```\napp/\n├── @modal/                    # Parallel route slot\n│   ├── default.tsx            # Required! Returns null\n│   ├── (.)photos/             # Intercepts /photos/*\n│   │   └── [id]/\n│   │       └── page.tsx       # Modal content\n│   └── [...]catchall/         # Optional: catch unmatched\n│       └── page.tsx\n├── photos/\n│   └── [id]/\n│       └── page.tsx           # Full page (direct access)\n├── layout.tsx                 # Renders both children and @modal\n└── page.tsx\n```\n\n## Step 1: Root Layout with Slot\n\n```tsx\n// app/layout.tsx\nexport default function RootLayout({ children, modal }: { children: React.ReactNode; modal: React.ReactNode }) {\n  return (\n    <html>\n      <body>\n        {children}\n        {modal}\n      </body>\n    </html>\n  )\n}\n```\n\n## Step 2: Default File (Critical!)\n\n**Every parallel route slot MUST have a `default.tsx`** to prevent 404s on hard navigation.\n\n```tsx\n// app/@modal/default.tsx\nexport default function Default() {\n  return null\n}\n```\n\nWithout this file, refreshing any page will 404 because Next.js can't determine what to render in the `@modal` slot.\n\n## Step 3: Intercepting Route (Modal)\n\nThe `(.)` prefix intercepts routes at the same level.\n\n```tsx\n// app/@modal/(.)photos/[id]/page.tsx\nimport { Modal } from '@/components/modal'\n\nexport default async function PhotoModal({ params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n  const photo = await getPhoto(id)\n\n  return (\n    <Modal>\n      <img src={photo.url} alt={photo.title} />\n    </Modal>\n  )\n}\n```\n\n## Step 4: Full Page (Direct Access)\n\n```tsx\n// app/photos/[id]/page.tsx\nexport default async function PhotoPage({ params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n  const photo = await getPhoto(id)\n\n  return (\n    <div className=\"full-page\">\n      <img src={photo.url} alt={photo.title} />\n      <h1>{photo.title}</h1>\n    </div>\n  )\n}\n```\n\n## Step 5: Modal Component with Correct Closing\n\n**Critical: Use `router.back()` to close modals, NOT `router.push()` or `<Link>`.**\n\n```tsx\n// components/modal.tsx\n'use client'\n\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useRef } from 'react'\n\nexport function Modal({ children }: { children: React.ReactNode }) {\n  const router = useRouter()\n  const overlayRef = useRef<HTMLDivElement>(null)\n\n  // Close on escape key\n  useEffect(() => {\n    function onKeyDown(e: KeyboardEvent) {\n      if (e.key === 'Escape') {\n        router.back() // Correct\n      }\n    }\n    document.addEventListener('keydown', onKeyDown)\n    return () => document.removeEventListener('keydown', onKeyDown)\n  }, [router])\n\n  // Close on overlay click\n  const handleOverlayClick = useCallback(\n    (e: React.MouseEvent) => {\n      if (e.target === overlayRef.current) {\n        router.back() // Correct\n      }\n    },\n    [router],\n  )\n\n  return (\n    <div\n      ref={overlayRef}\n      onClick={handleOverlayClick}\n      className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\"\n    >\n      <div className=\"mx-4 w-full max-w-2xl rounded-lg bg-white p-6\">\n        <button\n          onClick={() => router.back()} // Correct!\n          className=\"absolute top-4 right-4\"\n        >\n          Close\n        </button>\n        {children}\n      </div>\n    </div>\n  )\n}\n```\n\n### Why NOT `router.push('/')` or `<Link href=\"/\">`?\n\nUsing `push` or `Link` to \"close\" a modal:\n\n1. Adds a new history entry (back button shows modal again)\n2. Doesn't properly clear the intercepted route\n3. Can cause the modal to flash or persist unexpectedly\n\n`router.back()` correctly:\n\n1. Removes the intercepted route from history\n2. Returns to the previous page\n3. Properly unmounts the modal\n\n## Route Matcher Reference\n\nMatchers match **route segments**, not filesystem paths:\n\n| Matcher    | Matches       | Example                                                               |\n| ---------- | ------------- | --------------------------------------------------------------------- |\n| `(.)`      | Same level    | `@modal/(.)photos` intercepts `/photos`                               |\n| `(..)`     | One level up  | `@modal/(..)settings` from `/dashboard/@modal` intercepts `/settings` |\n| `(..)(..)` | Two levels up | Rarely used                                                           |\n| `(...)`    | From root     | `@modal/(...)photos` intercepts `/photos` from anywhere               |\n\n**Common mistake**: Thinking `(..)` means \"parent folder\" - it means \"parent route segment\".\n\n## Handling Hard Navigation\n\nWhen users directly visit `/photos/123` (bookmark, refresh, shared link):\n\n- The intercepting route is bypassed\n- The full `photos/[id]/page.tsx` renders\n- Modal doesn't appear (expected behavior)\n\nIf you want the modal to appear on direct access too, you need additional logic:\n\n```tsx\n// app/photos/[id]/page.tsx\nimport { Modal } from '@/components/modal'\n\nexport default async function PhotoPage({ params }) {\n  const { id } = await params\n  const photo = await getPhoto(id)\n\n  // Option: Render as modal on direct access too\n  return (\n    <Modal>\n      <img src={photo.url} alt={photo.title} />\n    </Modal>\n  )\n}\n```\n\n## Common Gotchas\n\n### 1. Missing `default.tsx` → 404 on Refresh\n\nEvery `@slot` folder needs a `default.tsx` that returns `null` (or appropriate content).\n\n### 2. Modal Persists After Navigation\n\nYou're using `router.push()` instead of `router.back()`.\n\n### 3. Nested Parallel Routes Need Defaults Too\n\nIf you have `@modal` inside a route group, each level needs its own `default.tsx`:\n\n```\napp/\n├── (marketing)/\n│   ├── @modal/\n│   │   └── default.tsx     # Needed!\n│   └── layout.tsx\n└── layout.tsx\n```\n\n### 4. Intercepted Route Shows Wrong Content\n\nCheck your matcher:\n\n- `(.)photos` intercepts `/photos` from the same route level\n- If your `@modal` is in `app/dashboard/@modal`, use `(.)photos` to intercept `/dashboard/photos`, not `/photos`\n\n### 5. TypeScript Errors with `params`\n\nIn Next.js 15+, `params` is a Promise:\n\n```tsx\n// Correct\nexport default async function Page({ params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n}\n```\n\n## Complete Example: Photo Gallery Modal\n\n```\napp/\n├── @modal/\n│   ├── default.tsx\n│   └── (.)photos/\n│       └── [id]/\n│           └── page.tsx\n├── photos/\n│   ├── page.tsx           # Gallery grid\n│   └── [id]/\n│       └── page.tsx       # Full photo page\n├── layout.tsx\n└── page.tsx\n```\n\nLinks in the gallery:\n\n```tsx\n// app/photos/page.tsx\nimport Link from 'next/link'\n\nexport default async function Gallery() {\n  const photos = await getPhotos()\n\n  return (\n    <div className=\"grid grid-cols-3 gap-4\">\n      {photos.map((photo) => (\n        <Link key={photo.id} href={`/photos/${photo.id}`}>\n          <img src={photo.thumbnail} alt={photo.title} />\n        </Link>\n      ))}\n    </div>\n  )\n}\n```\n\nClicking a photo → Modal opens (intercepted)\nDirect URL → Full page renders\nRefresh while modal open → Full page renders\n",".agents/skills/next-best-practices/route-handlers.md":"# Route Handlers\n\nCreate API endpoints with `route.ts` files.\n\n## Basic Usage\n\n```tsx\n// app/api/users/route.ts\nexport async function GET() {\n  const users = await getUsers()\n  return Response.json(users)\n}\n\nexport async function POST(request: Request) {\n  const body = await request.json()\n  const user = await createUser(body)\n  return Response.json(user, { status: 201 })\n}\n```\n\n## Supported Methods\n\n`GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`\n\n## GET Handler Conflicts with page.tsx\n\n**A `route.ts` and `page.tsx` cannot coexist in the same folder.**\n\n```\napp/\n├── api/\n│   └── users/\n│       └── route.ts    # /api/users\n└── users/\n    ├── page.tsx        # /users (page)\n    └── route.ts        # Warning: Conflicts with page.tsx!\n```\n\nIf you need both a page and an API at the same path, use different paths:\n\n```\napp/\n├── users/\n│   └── page.tsx        # /users (page)\n└── api/\n    └── users/\n        └── route.ts    # /api/users (API)\n```\n\n## Environment Behavior\n\nRoute handlers run in a **Server Component-like environment**:\n\n- Yes: Can use `async/await`\n- Yes: Can access `cookies()`, `headers()`\n- Yes: Can use Node.js APIs\n- No: Cannot use React hooks\n- No: Cannot use React DOM APIs\n- No: Cannot use browser APIs\n\n```tsx\n// Bad: This won't work - no React DOM in route handlers\nimport { renderToString } from 'react-dom/server'\n\nexport async function GET() {\n  const html = renderToString(<Component />) // Error!\n  return new Response(html)\n}\n```\n\n## Dynamic Route Handlers\n\n```tsx\n// app/api/users/[id]/route.ts\nexport async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n  const user = await getUser(id)\n\n  if (!user) {\n    return Response.json({ error: 'Not found' }, { status: 404 })\n  }\n\n  return Response.json(user)\n}\n```\n\n## Request Helpers\n\n```tsx\nexport async function GET(request: Request) {\n  // URL and search params\n  const { searchParams } = new URL(request.url)\n  const query = searchParams.get('q')\n\n  // Headers\n  const authHeader = request.headers.get('authorization')\n\n  // Cookies (Next.js helper)\n  const cookieStore = await cookies()\n  const token = cookieStore.get('token')\n\n  return Response.json({ query, token })\n}\n```\n\n## Response Helpers\n\n```tsx\n// JSON response\nreturn Response.json({ data })\n\n// With status\nreturn Response.json({ error: 'Not found' }, { status: 404 })\n\n// With headers\nreturn Response.json(data, {\n  headers: {\n    'Cache-Control': 'max-age=3600',\n  },\n})\n\n// Redirect\nreturn Response.redirect(new URL('/login', request.url))\n\n// Stream\nreturn new Response(stream, {\n  headers: { 'Content-Type': 'text/event-stream' },\n})\n```\n\n## When to Use Route Handlers vs Server Actions\n\n| Use Case                 | Route Handlers | Server Actions |\n| ------------------------ | -------------- | -------------- |\n| Form submissions         | No             | Yes            |\n| Data mutations from UI   | No             | Yes            |\n| Third-party webhooks     | Yes            | No             |\n| External API consumption | Yes            | No             |\n| Public REST API          | Yes            | No             |\n| File uploads             | Both work      | Both work      |\n\n**Prefer Server Actions** for mutations triggered from your UI.\n**Use Route Handlers** for external integrations and public APIs.\n",".agents/skills/next-best-practices/rsc-boundaries.md":"# RSC Boundaries\n\nDetect and prevent invalid patterns when crossing Server/Client component boundaries.\n\n## Detection Rules\n\n### 1. Async Client Components Are Invalid\n\nClient components **cannot** be async functions. Only Server Components can be async.\n\n**Detect:** File has `'use client'` AND component is `async function` or returns `Promise`\n\n```tsx\n// Bad: async client component\n'use client'\nexport default async function UserProfile() {\n  const user = await getUser() // Cannot await in client component\n  return <div>{user.name}</div>\n}\n\n// Good: Remove async, fetch data in parent server component\n// page.tsx (server component - no 'use client')\nexport default async function Page() {\n  const user = await getUser()\n  return <UserProfile user={user} />\n}\n\n// UserProfile.tsx (client component)\n;('use client')\nexport function UserProfile({ user }: { user: User }) {\n  return <div>{user.name}</div>\n}\n```\n\n```tsx\n// Bad: async arrow function client component\n'use client'\nconst Dashboard = async () => {\n  const data = await fetchDashboard()\n  return <div>{data}</div>\n}\n\n// Good: Fetch in server component, pass data down\n```\n\n### 2. Non-Serializable Props to Client Components\n\nProps passed from Server → Client must be JSON-serializable.\n\n**Detect:** Server component passes these to a client component:\n\n- Functions (except Server Actions with `'use server'`)\n- `Date` objects\n- `Map`, `Set`, `WeakMap`, `WeakSet`\n- Class instances\n- `Symbol` (unless globally registered)\n- Circular references\n\n```tsx\n// Bad: Function prop\n// page.tsx (server)\nexport default function Page() {\n  const handleClick = () => console.log('clicked')\n  return <ClientButton onClick={handleClick} />\n}\n\n// Good: Define function inside client component\n// ClientButton.tsx\n;('use client')\nexport function ClientButton() {\n  const handleClick = () => console.log('clicked')\n  return <button onClick={handleClick}>Click</button>\n}\n```\n\n```tsx\n// Bad: Date object (silently becomes string, then crashes)\n// page.tsx (server)\nexport default async function Page() {\n  const post = await getPost()\n  return <PostCard createdAt={post.createdAt} /> // Date object\n}\n\n// PostCard.tsx (client) - will crash on .getFullYear()\n;('use client')\nexport function PostCard({ createdAt }: { createdAt: Date }) {\n  return <span>{createdAt.getFullYear()}</span> // Runtime error!\n}\n\n// Good: Serialize to string on server\n// page.tsx (server)\nexport default async function Page() {\n  const post = await getPost()\n  return <PostCard createdAt={post.createdAt.toISOString()} />\n}\n\n// PostCard.tsx (client)\n;('use client')\nexport function PostCard({ createdAt }: { createdAt: string }) {\n  const date = new Date(createdAt)\n  return <span>{date.getFullYear()}</span>\n}\n```\n\n```tsx\n// Bad: Class instance\nconst user = new UserModel(data)\n<ClientProfile user={user} /> // Methods will be stripped\n\n// Good: Pass plain object\nconst user = await getUser()\n<ClientProfile user={{ id: user.id, name: user.name }} />\n```\n\n```tsx\n// Bad: Map/Set\n<ClientComponent items={new Map([['a', 1]])} />\n\n// Good: Convert to array/object\n<ClientComponent items={Object.fromEntries(map)} />\n<ClientComponent items={Array.from(set)} />\n```\n\n### 3. Server Actions Are the Exception\n\nFunctions marked with `'use server'` CAN be passed to client components.\n\n```tsx\n// Valid: Server Action can be passed\n// actions.ts\n'use server'\nexport async function submitForm(formData: FormData) {\n  // server-side logic\n}\n\n// page.tsx (server)\nimport { submitForm } from './actions'\nexport default function Page() {\n  return <ClientForm onSubmit={submitForm} /> // OK!\n}\n\n// ClientForm.tsx (client)\n;('use client')\nexport function ClientForm({ onSubmit }: { onSubmit: (data: FormData) => Promise<void> }) {\n  return <form action={onSubmit}>...</form>\n}\n```\n\n## Quick Reference\n\n| Pattern                           | Valid? | Fix                                   |\n| --------------------------------- | ------ | ------------------------------------- |\n| `'use client'` + `async function` | No     | Fetch in server parent, pass data     |\n| Pass `() => {}` to client         | No     | Define in client or use server action |\n| Pass `new Date()` to client       | No     | Use `.toISOString()`                  |\n| Pass `new Map()` to client        | No     | Convert to object/array               |\n| Pass class instance to client     | No     | Pass plain object                     |\n| Pass server action to client      | Yes    | -                                     |\n| Pass `string/number/boolean`      | Yes    | -                                     |\n| Pass plain object/array           | Yes    | -                                     |\n",".agents/skills/next-best-practices/runtime-selection.md":"# Runtime Selection\n\n## Use Node.js Runtime by Default\n\nUse the default Node.js runtime for new routes and pages. Only use Edge runtime if the project already uses it or there's a specific requirement.\n\n```tsx\n// Good: Default - no runtime config needed (uses Node.js)\nexport default function Page() { ... }\n\n// Caution: Only if already used in project or specifically required\nexport const runtime = 'edge'\n```\n\n## When to Use Each\n\n### Node.js Runtime (Default)\n\n- Full Node.js API support\n- File system access (`fs`)\n- Full `crypto` support\n- Database connections\n- Most npm packages work\n\n### Edge Runtime\n\n- Only for specific edge-location latency requirements\n- Limited API (no `fs`, limited `crypto`)\n- Smaller cold start\n- Geographic distribution needs\n\n## Detection\n\n**Before adding `runtime = 'edge'`**, check:\n\n1. Does the project already use Edge runtime?\n2. Is there a specific latency requirement?\n3. Are all dependencies Edge-compatible?\n\nIf unsure, use Node.js runtime.\n",".agents/skills/next-best-practices/scripts.md":"# Scripts\n\nLoading third-party scripts in Next.js.\n\n## Use next/script\n\nAlways use `next/script` instead of native `<script>` tags for better performance.\n\n```tsx\n// Bad: Native script tag\n;<script src=\"https://example.com/script.js\"></script>\n\n// Good: Next.js Script component\nimport Script from 'next/script'\n;<Script src=\"https://example.com/script.js\" />\n```\n\n## Inline Scripts Need ID\n\nInline scripts require an `id` attribute for Next.js to track them.\n\n```tsx\n// Bad: Missing id\n<Script dangerouslySetInnerHTML={{ __html: 'console.log(\"hi\")' }} />\n\n// Good: Has id\n<Script id=\"my-script\" dangerouslySetInnerHTML={{ __html: 'console.log(\"hi\")' }} />\n\n// Good: Inline with id\n<Script id=\"show-banner\">\n  {`document.getElementById('banner').classList.remove('hidden')`}\n</Script>\n```\n\n## Don't Put Script in Head\n\n`next/script` should not be placed inside `next/head`. It handles its own positioning.\n\n```tsx\n// Bad: Script inside Head\nimport Head from 'next/head'\nimport Script from 'next/script'\n\n<Head>\n  <Script src=\"/analytics.js\" />\n</Head>\n\n// Good: Script outside Head\n<Head>\n  <title>Page</title>\n</Head>\n<Script src=\"/analytics.js\" />\n```\n\n## Loading Strategies\n\n```tsx\n// afterInteractive (default) - Load after page is interactive\n<Script src=\"/analytics.js\" strategy=\"afterInteractive\" />\n\n// lazyOnload - Load during idle time\n<Script src=\"/widget.js\" strategy=\"lazyOnload\" />\n\n// beforeInteractive - Load before page is interactive (use sparingly)\n// Only works in app/layout.tsx or pages/_document.js\n<Script src=\"/critical.js\" strategy=\"beforeInteractive\" />\n\n// worker - Load in web worker (experimental)\n<Script src=\"/heavy.js\" strategy=\"worker\" />\n```\n\n## Google Analytics\n\nUse `@next/third-parties` instead of inline GA scripts.\n\n```tsx\n// Bad: Inline GA script\n<Script src=\"https://www.googletagmanager.com/gtag/js?id=G-XXXXX\" />\n<Script id=\"ga-init\">\n  {`window.dataLayer = window.dataLayer || [];\n    function gtag(){dataLayer.push(arguments);}\n    gtag('js', new Date());\n    gtag('config', 'G-XXXXX');`}\n</Script>\n\n// Good: Next.js component\nimport { GoogleAnalytics } from '@next/third-parties/google'\n\nexport default function Layout({ children }) {\n  return (\n    <html>\n      <body>{children}</body>\n      <GoogleAnalytics gaId=\"G-XXXXX\" />\n    </html>\n  )\n}\n```\n\n## Google Tag Manager\n\n```tsx\nimport { GoogleTagManager } from '@next/third-parties/google'\n\nexport default function Layout({ children }) {\n  return (\n    <html>\n      <GoogleTagManager gtmId=\"GTM-XXXXX\" />\n      <body>{children}</body>\n    </html>\n  )\n}\n```\n\n## Other Third-Party Scripts\n\n```tsx\n// YouTube embed\nimport { YouTubeEmbed } from '@next/third-parties/google'\n;<YouTubeEmbed videoid=\"dQw4w9WgXcQ\" />\n\n// Google Maps\nimport { GoogleMapsEmbed } from '@next/third-parties/google'\n;<GoogleMapsEmbed apiKey=\"YOUR_API_KEY\" mode=\"place\" q=\"Brooklyn+Bridge,New+York,NY\" />\n```\n\n## Quick Reference\n\n| Pattern                                       | Issue                      | Fix                       |\n| --------------------------------------------- | -------------------------- | ------------------------- |\n| `<script src=\"...\">`                          | No optimization            | Use `next/script`         |\n| `<Script>` without id                         | Can't track inline scripts | Add `id` attribute        |\n| `<Script>` inside `<Head>`                    | Wrong placement            | Move outside Head         |\n| Inline GA/GTM scripts                         | No optimization            | Use `@next/third-parties` |\n| `strategy=\"beforeInteractive\"` outside layout | Won't work                 | Only use in root layout   |\n",".agents/skills/next-best-practices/self-hosting.md":"# Self-Hosting Next.js\n\nDeploy Next.js outside of Vercel with confidence.\n\n## Quick Start: Standalone Output\n\nFor Docker or any containerized deployment, use standalone output:\n\n```js\n// next.config.js\nmodule.exports = {\n  output: 'standalone',\n}\n```\n\nThis creates a minimal `standalone` folder with only production dependencies:\n\n```\n.next/\n├── standalone/\n│   ├── server.js          # Entry point\n│   ├── node_modules/      # Only production deps\n│   └── .next/             # Build output\n└── static/                # Must be copied separately\n```\n\n## Docker Deployment\n\n### Dockerfile\n\n```dockerfile\nFROM node:20-alpine AS base\n\n# Install dependencies\nFROM base AS deps\nWORKDIR /app\nCOPY package.json package-lock.json* ./\nRUN npm ci\n\n# Build\nFROM base AS builder\nWORKDIR /app\nCOPY --from=deps /app/node_modules ./node_modules\nCOPY . .\nRUN npm run build\n\n# Production\nFROM base AS runner\nWORKDIR /app\n\nENV NODE_ENV=production\n\n# Create non-root user\nRUN addgroup --system --gid 1001 nodejs\nRUN adduser --system --uid 1001 nextjs\n\n# Copy standalone output\nCOPY --from=builder /app/.next/standalone ./\nCOPY --from=builder /app/.next/static ./.next/static\nCOPY --from=builder /app/public ./public\n\nUSER nextjs\n\nEXPOSE 3000\nENV PORT=3000\nENV HOSTNAME=\"0.0.0.0\"\n\nCMD [\"node\", \"server.js\"]\n```\n\n### Docker Compose\n\n```yaml\nversion: '3.8'\n\nservices:\n  web:\n    build: .\n    ports:\n      - '3000:3000'\n    environment:\n      - NODE_ENV=production\n    restart: unless-stopped\n    healthcheck:\n      test: ['CMD', 'wget', '-q', '--spider', 'http://localhost:3000/api/health']\n      interval: 30s\n      timeout: 10s\n      retries: 3\n```\n\n## PM2 Deployment\n\nFor traditional server deployments:\n\n```js\n// ecosystem.config.js\nmodule.exports = {\n  apps: [\n    {\n      name: 'nextjs',\n      script: '.next/standalone/server.js',\n      instances: 'max',\n      exec_mode: 'cluster',\n      env: {\n        NODE_ENV: 'production',\n        PORT: 3000,\n      },\n    },\n  ],\n}\n```\n\n```bash\nnpm run build\npm2 start ecosystem.config.js\n```\n\n## ISR and Cache Handlers\n\n### The Problem\n\nISR (Incremental Static Regeneration) uses filesystem caching by default. This **breaks with multiple instances**:\n\n- Instance A regenerates page → saves to its local disk\n- Instance B serves stale page → doesn't see Instance A's cache\n- Load balancer sends users to random instances → inconsistent content\n\n### Solution: Custom Cache Handler\n\nNext.js 14+ supports custom cache handlers for shared storage:\n\n```js\n// next.config.js\nmodule.exports = {\n  cacheHandler: require.resolve('./cache-handler.js'),\n  cacheMaxMemorySize: 0, // Disable in-memory cache\n}\n```\n\n#### Redis Cache Handler Example\n\n```js\n// cache-handler.js\nconst Redis = require('ioredis')\n\nconst redis = new Redis(process.env.REDIS_URL)\nconst CACHE_PREFIX = 'nextjs:'\n\nmodule.exports = class CacheHandler {\n  constructor(options) {\n    this.options = options\n  }\n\n  async get(key) {\n    const data = await redis.get(CACHE_PREFIX + key)\n    if (!data) return null\n\n    const parsed = JSON.parse(data)\n    return {\n      value: parsed.value,\n      lastModified: parsed.lastModified,\n    }\n  }\n\n  async set(key, data, ctx) {\n    const cacheData = {\n      value: data,\n      lastModified: Date.now(),\n    }\n\n    // Set TTL based on revalidate option\n    if (ctx?.revalidate) {\n      await redis.setex(CACHE_PREFIX + key, ctx.revalidate, JSON.stringify(cacheData))\n    } else {\n      await redis.set(CACHE_PREFIX + key, JSON.stringify(cacheData))\n    }\n  }\n\n  async revalidateTag(tags) {\n    // Implement tag-based invalidation\n    // This requires tracking which keys have which tags\n  }\n}\n```\n\n#### S3 Cache Handler Example\n\n```js\n// cache-handler.js\nconst { S3Client, GetObjectCommand, PutObjectCommand } = require('@aws-sdk/client-s3')\n\nconst s3 = new S3Client({ region: process.env.AWS_REGION })\nconst BUCKET = process.env.CACHE_BUCKET\n\nmodule.exports = class CacheHandler {\n  async get(key) {\n    try {\n      const response = await s3.send(\n        new GetObjectCommand({\n          Bucket: BUCKET,\n          Key: `cache/${key}`,\n        }),\n      )\n      const body = await response.Body.transformToString()\n      return JSON.parse(body)\n    } catch (err) {\n      if (err.name === 'NoSuchKey') return null\n      throw err\n    }\n  }\n\n  async set(key, data, ctx) {\n    await s3.send(\n      new PutObjectCommand({\n        Bucket: BUCKET,\n        Key: `cache/${key}`,\n        Body: JSON.stringify({\n          value: data,\n          lastModified: Date.now(),\n        }),\n        ContentType: 'application/json',\n      }),\n    )\n  }\n}\n```\n\n## What Works vs What Needs Setup\n\n| Feature              | Single Instance | Multi-Instance      | Notes                       |\n| -------------------- | --------------- | ------------------- | --------------------------- |\n| SSR                  | Yes             | Yes                 | No special setup            |\n| SSG                  | Yes             | Yes                 | Built at deploy time        |\n| ISR                  | Yes             | Needs cache handler | Filesystem cache breaks     |\n| Image Optimization   | Yes             | Yes                 | CPU-intensive, consider CDN |\n| Middleware           | Yes             | Yes                 | Runs on Node.js             |\n| Edge Runtime         | Limited         | Limited             | Some features Node-only     |\n| `revalidatePath/Tag` | Yes             | Needs cache handler | Must share cache            |\n| `next/font`          | Yes             | Yes                 | Fonts bundled at build      |\n| Draft Mode           | Yes             | Yes                 | Cookie-based                |\n\n## Image Optimization\n\nNext.js Image Optimization works out of the box but is CPU-intensive.\n\n### Option 1: Built-in (Simple)\n\nWorks automatically, but consider:\n\n- Set `deviceSizes` and `imageSizes` in config to limit variants\n- Use `minimumCacheTTL` to reduce regeneration\n\n```js\n// next.config.js\nmodule.exports = {\n  images: {\n    minimumCacheTTL: 60 * 60 * 24, // 24 hours\n    deviceSizes: [640, 750, 1080, 1920], // Limit sizes\n  },\n}\n```\n\n### Option 2: External Loader (Recommended for Scale)\n\nOffload to Cloudinary, Imgix, or similar:\n\n```js\n// next.config.js\nmodule.exports = {\n  images: {\n    loader: 'custom',\n    loaderFile: './lib/image-loader.js',\n  },\n}\n```\n\n```js\n// lib/image-loader.js\nexport default function cloudinaryLoader({ src, width, quality }) {\n  const params = ['f_auto', 'c_limit', `w_${width}`, `q_${quality || 'auto'}`]\n  return `https://res.cloudinary.com/demo/image/upload/${params.join(',')}${src}`\n}\n```\n\n## Environment Variables\n\n### Build-time vs Runtime\n\n```js\n// Available at build time only (baked into bundle)\nNEXT_PUBLIC_API_URL=https://api.example.com\n\n// Available at runtime (server-side only)\nDATABASE_URL=postgresql://...\nAPI_SECRET=...\n```\n\n### Runtime Configuration\n\nFor truly dynamic config, don't use `NEXT_PUBLIC_*`. Instead:\n\n```tsx\n// app/api/config/route.ts\nexport async function GET() {\n  return Response.json({\n    apiUrl: process.env.API_URL,\n    features: process.env.FEATURES?.split(','),\n  })\n}\n```\n\n## OpenNext: Serverless Without Vercel\n\n[OpenNext](https://open-next.js.org/) adapts Next.js for AWS Lambda, Cloudflare Workers, etc.\n\n```bash\nnpx create-sst@latest\n# or\nnpx @opennextjs/aws build\n```\n\nSupports:\n\n- AWS Lambda + CloudFront\n- Cloudflare Workers\n- Netlify Functions\n- Deno Deploy\n\n## Health Check Endpoint\n\nAlways include a health check for load balancers:\n\n```tsx\n// app/api/health/route.ts\nexport async function GET() {\n  try {\n    // Optional: check database connection\n    // await db.$queryRaw`SELECT 1`;\n\n    return Response.json({ status: 'healthy' }, { status: 200 })\n  } catch (error) {\n    return Response.json({ status: 'unhealthy' }, { status: 503 })\n  }\n}\n```\n\n## Pre-Deployment Checklist\n\n1. **Build locally first**: `npm run build` - catch errors before deploy\n2. **Test standalone output**: `node .next/standalone/server.js`\n3. **Set `output: 'standalone'`** for Docker\n4. **Configure cache handler** for multi-instance ISR\n5. **Set `HOSTNAME=\"0.0.0.0\"`** for containers\n6. **Copy `public/` and `.next/static/`** - not included in standalone\n7. **Add health check endpoint**\n8. **Test ISR revalidation** after deployment\n9. **Monitor memory usage** - Node.js defaults may need tuning\n\n## Testing Cache Handler\n\n**Critical**: Test your cache handler on every Next.js upgrade:\n\n```bash\n# Start multiple instances\nPORT=3001 node .next/standalone/server.js &\nPORT=3002 node .next/standalone/server.js &\n\n# Trigger ISR revalidation\ncurl http://localhost:3001/api/revalidate?path=/posts\n\n# Verify both instances see the update\ncurl http://localhost:3001/posts\ncurl http://localhost:3002/posts\n# Should return identical content\n```\n",".agents/skills/next-best-practices/suspense-boundaries.md":"# Suspense Boundaries\n\nClient hooks that cause CSR bailout without Suspense boundaries.\n\n## useSearchParams\n\nAlways requires Suspense boundary in static routes. Without it, the entire page becomes client-side rendered.\n\n```tsx\n// Bad: Entire page becomes CSR\n'use client'\n\nimport { useSearchParams } from 'next/navigation'\n\nexport default function SearchBar() {\n  const searchParams = useSearchParams()\n  return <div>Query: {searchParams.get('q')}</div>\n}\n```\n\n```tsx\n// Good: Wrap in Suspense\nimport { Suspense } from 'react'\nimport SearchBar from './search-bar'\n\nexport default function Page() {\n  return (\n    <Suspense fallback={<div>Loading...</div>}>\n      <SearchBar />\n    </Suspense>\n  )\n}\n```\n\n## usePathname\n\nRequires Suspense boundary when route has dynamic parameters.\n\n```tsx\n// In dynamic route [slug]\n// Bad: No Suspense\n'use client'\nimport { usePathname } from 'next/navigation'\n\nexport function Breadcrumb() {\n  const pathname = usePathname()\n  return <nav>{pathname}</nav>\n}\n```\n\n```tsx\n// Good: Wrap in Suspense\n<Suspense fallback={<BreadcrumbSkeleton />}>\n  <Breadcrumb />\n</Suspense>\n```\n\nIf you use `generateStaticParams`, Suspense is optional.\n\n## Quick Reference\n\n| Hook                | Suspense Required    |\n| ------------------- | -------------------- |\n| `useSearchParams()` | Yes                  |\n| `usePathname()`     | Yes (dynamic routes) |\n| `useParams()`       | No                   |\n| `useRouter()`       | No                   |\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([fetchUser(), fetchConfig()])\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() {\n    return fetchUser()\n  },\n  async config() {\n    return fetchConfig()\n  },\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([userPromise, fetchConfig(), profilePromise])\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([configPromise, fetchData(session.user.id)])\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([fetchUser(), fetchPosts(), fetchComments()])\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({\n  enabled,\n  setEnabled,\n}: {\n  enabled: boolean\n  setEnabled: React.Dispatch<React.SetStateAction<boolean>>\n}) {\n  const [frames, setFrames] = useState<Frame[] | null>(null)\n\n  useEffect(() => {\n    if (enabled && !frames && typeof window !== 'undefined') {\n      import('./animation-frames.js').then((mod) => setFrames(mod.frames)).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(() => import('@vercel/analytics/react').then((m) => m.Analytics), { ssr: false })\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(() => import('./monaco-editor').then((m) => m.MonacoEditor), { ssr: false })\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 onMouseEnter={preload} onFocus={preload} onClick={onClick}>\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}>{children}</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    /* ... */\n  })\n  useKeyboardShortcut('k', () => {\n    /* ... */\n  })\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(\n      'prefs:v1',\n      JSON.stringify({\n        theme: user.preferences.theme,\n        notifications: user.preferences.notifications,\n      }),\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(\n    (newItems: Item[]) => {\n      setItems([...items, ...newItems])\n    },\n    [items],\n  ) // ❌ 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: MEDIUM (wasted computation on every render)**\n\nPass a functi\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\n   ```bash\n   pnpm install\n   ```\n\n2. Build AGENTS.md from rules:\n\n   ```bash\n   pnpm build\n   ```\n\n3. Validate rule files:\n\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\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/JimmyLv/BibiGPT-v1?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([configPromise, fetchData(session.user.id)])\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([fetchUser(), fetchConfig()])\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() {\n    return fetchUser()\n  },\n  async config() {\n    return fetchConfig()\n  },\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([userPromise, fetchConfig(), profilePromise])\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([fetchUser(), fetchPosts(), fetchComments()])\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({\n  enabled,\n  setEnabled,\n}: {\n  enabled: boolean\n  setEnabled: React.Dispatch<React.SetStateAction<boolean>>\n}) {\n  const [frames, setFrames] = useState<Frame[] | null>(null)\n\n  useEffect(() => {\n    if (enabled && !frames && typeof window !== 'undefined') {\n      import('./animation-frames.js').then((mod) => setFrames(mod.frames)).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(() => import('@vercel/analytics/react').then((m) => m.Analytics), { ssr: false })\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(() => import('./monaco-editor').then((m) => m.MonacoEditor), { ssr: false })\n\nfunction CodePanel({ code }: { code: string }) {\n  return <MonacoEditor value={code} />\n}\n```\n",".agents/skills/vercel-react-best-practices/rules/bundle-preload.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fbundle-preload.md -->",".agents/skills/vercel-react-best-practices/rules/client-event-listeners.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fclient-event-listeners.md -->",".agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fclient-localstorage-schema.md -->",".agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fclient-passive-event-listeners.md -->",".agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fclient-swr-dedup.md -->",".agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-batch-dom-css.md -->",".agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-cache-function-results.md -->",".agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-cache-property-access.md -->",".agents/skills/vercel-react-best-practices/rules/js-cache-storage.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-cache-storage.md -->",".agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-combine-iterations.md -->",".agents/skills/vercel-react-best-practices/rules/js-early-exit.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-early-exit.md -->",".agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-hoist-regexp.md -->",".agents/skills/vercel-react-best-practices/rules/js-index-maps.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-index-maps.md -->",".agents/skills/vercel-react-best-practices/rules/js-length-check-first.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-length-check-first.md -->",".agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-min-max-loop.md -->",".agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fserver-serialization.md -->",".agents/skills/web-design-guidelines/SKILL.md":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fweb-design-guidelines%2FSKILL.md -->","AGENTS.md":"# Repository Guidelines\n\n## Project Structure & Module Organization\n\nThis repository is a Next.js + TypeScript app. Keep feature changes close to existing folders:\n\n- `pages/`: route entry points (`pages/api/` for server endpoints).\n- `components/`: UI and reusable view logic (`components/ui/` for primitive controls).\n- `hooks/`: custom React hooks.\n- `lib/`: integration logic (OpenAI, Supabase, Upstash, LemonSqueezy).\n- `utils/`: pure helpers and schema utilities (`utils/schemas/`).\n- `styles/`: global and markdown styles.\n- `public/`: static assets and images.\n\n## Build, Test, and Development Commands\n\nUse `npm` with the committed lockfile:\n\n- `npm ci`: install exact dependencies.\n- `npm run dev`: start local dev server at `http://localhost:3000`.\n- `npm run build`: production build (also catches type/build issues).\n- `npm run start`: run the built app.\n- `docker compose up -d`: run containerized service after creating `.env`.\n\nThere is no dedicated lint/test script. Use:\n\n- `npx prettier --check .`: formatting check.\n- `npx tsc --noEmit`: explicit TypeScript check.\n\n## Coding Style & Naming Conventions\n\n- TypeScript strict mode is enabled; prefer explicit, narrow types.\n- Formatting is enforced by Prettier (`singleQuote: true`, `semi: false`, `trailingComma: all`, `printWidth: 120`).\n- Pre-commit hook runs `lint-staged` with `prettier --write --ignore-unknown`.\n- Use naming patterns already present:\n  - Components: `PascalCase.tsx` (example: `SummaryResult.tsx`)\n  - Hooks: `useSomething.ts` (example: `useSummarize.ts`)\n  - Helpers: `camelCase.ts` (example: `extractUrl.ts`)\n- Prefer path aliases `@/` or `~/` for root imports.\n\n## Testing Guidelines\n\nNo automated test framework is configured yet. For every behavioral change, include manual verification notes in your PR:\n\n- Main summary flow from `pages/index.tsx`\n- Any affected API endpoint in `pages/api/`\n- At least one source parsing case (YouTube or Bilibili)\n\nIf adding tests, colocate them as `*.test.ts` or `*.test.tsx` near changed modules.\n\n## Commit & Pull Request Guidelines\n\n- Follow Conventional Commits used by release tooling: `feat:`, `fix:`, `docs:`, `chore:` (optional scope, e.g. `chore(main):`).\n- Keep commit subjects short and imperative.\n- PRs should include: purpose, key changes, config/env impact, linked issue(s), and UI screenshots when relevant.\n- Before requesting review, run `npm run build` and ensure formatting is clean.\n\n## Security & Configuration Tips\n\n- Copy `.example.env` to `.env`; never commit secrets.\n- Required core services are OpenAI and Upstash; Sentry/Lemon/Segment are optional.\n- Avoid sharing built Docker images that contain `.env`.\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# Repository Guidelines\n\n## Project Structure & Module Organization\n\nThis repository is a Next.js + TypeScript app. Keep feature changes close to existing folders:\n\n- `pages/`: route entry points (`pages/api/` for server endpoints).\n- `components/`: UI and reusable view logic (`components/ui/` for primitive controls).\n- `hooks/`: custom React hooks.\n- `lib/`: integration logic (OpenAI, Supabase, Upstash, LemonSqueezy).\n- `utils/`: pure helpers and schema utilities (`utils/schemas/`).\n- `styles/`: global and markdown styles.\n- `public/`: static assets and images.\n\n## Build, Test, and Development Commands\n\nUse `npm` with the committed lockfile:\n\n- `npm ci`: install exact dependencies.\n- `npm run dev`: start local dev server at `http://localhost:3000`.\n- `npm run build`: production build (also catches type/build issues).\n- `npm run start`: run the built app.\n- `docker compose up -d`: run containerized service after creating `.env`.\n\nThere is no dedicated lint/test script. Use:\n\n- `npx prettier --check .`: formatting check.\n- `npx tsc --noEmit`: explicit TypeScript check.\n\n## Coding Style & Naming Conventions\n\n- TypeScript strict mode is enabled; prefer explicit, narrow types.\n- Formatting is enforced by Prettier (`singleQuote: true`, `semi: false`, `trailingComma: all`, `printWidth: 120`).\n- Pre-commit hook runs `lint-staged` with `prettier --write --ignore-unknown`.\n- Use naming patterns already present:\n  - Components: `PascalCase.tsx` (example: `SummaryResult.tsx`)\n  - Hooks: `useSomething.ts` (example: `useSummarize.ts`)\n  - Helpers: `camelCase.ts` (example: `extractUrl.ts`)\n- Prefer path aliases `@/` or `~/` for root imports.\n\n## Testing Guidelines\n\nNo automated test framework is configured yet. For every behavioral change, include manual verification notes in your PR:\n\n- Main summary flow from `pages/index.tsx`\n- Any affected API endpoint in `pages/api/`\n- At least one source parsing case (YouTube or Bilibili)\n\nIf adding tests, colocate them as `*.test.ts` or `*.test.tsx` near changed modules.\n\n## Commit & Pull Request Guidelines\n\n- Follow Conventional Commits used by release tooling: `feat:`, `fix:`, `docs:`, `chore:` (optional scope, e.g. `chore(main):`).\n- Keep commit subjects short and imperative.\n- PRs should include: purpose, key changes, config/env impact, linked issue(s), and UI screenshots when relevant.\n- Before requesting review, run `npm run build` and ensure formatting is clean.\n\n## Security & Configuration Tips\n\n- Copy `.example.env` to `.env`; never commit secrets.\n- Required core services are OpenAI and Upstash; Sentry/Lemon/Segment are optional.\n- Avoid sharing built Docker images that contain `.env`.\n","isInternal":false,"tokens":653,"sizeBytes":2669},{"name":"authentication.md","path":".agents/skills/agent-browser/references/authentication.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/agent-browser/references/authentication.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Authentication Patterns\n\nLogin flows, session persistence, OAuth, 2FA, and authenticated browsing.\n\n**Related**: [session-management.md](session-management.md) for state persistence details, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [Basic Login Flow](#basic-login-flow)\n- [Saving Authentication State](#saving-authentication-state)\n- [Restoring Authentication](#restoring-authentication)\n- [OAuth / SSO Flows](#oauth--sso-flows)\n- [Two-Factor Authentication](#two-factor-authentication)\n- [HTTP Basic Auth](#http-basic-auth)\n- [Cookie-Based Auth](#cookie-based-auth)\n- [Token Refresh Handling](#token-refresh-handling)\n- [Security Best Practices](#security-best-practices)\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\n   ```bash\n   echo \"*.auth-state.json\" >> .gitignore\n   ```\n\n2. **Use environment variables for credentials**\n\n   ```bash\n   agent-browser fill @e1 \"$APP_USERNAME\"\n   agent-browser fill @e2 \"$APP_PASSWORD\"\n   ```\n\n3. **Clean up after automation**\n\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":1188,"sizeBytes":5026},{"name":"commands.md","path":".agents/skills/agent-browser/references/commands.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/agent-browser/references/commands.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Command Reference\n\nComplete reference for all agent-browser commands. For quick start and common patterns, see SKILL.md.\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 click @e1 --new-tab # Click and open in new tab\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 and PDF\n\n```bash\nagent-browser screenshot          # Save to temporary directory\nagent-browser screenshot path.png # Save to 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\nagent-browser click @e1                   # Perform actions\nagent-browser record stop                 # Stop and save video\nagent-browser record restart ./take2.webm # Stop current + start new\n```\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 and 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 and 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\"          # Simple expressions only\nagent-browser eval -b \"<base64>\"             # Any JavaScript (base64 encoded)\nagent-browser eval --stdin                   # Read script from stdin\n```\n\nUse `-b`/`--base64` or `--stdin` for reliable execution. Shell escaping with nested quotes and special characters is error-prone.\n\n```bash\n# Base64 encode your script, then:\nagent-browser eval -b \"ZG9jdW1lbnQucXVlcnlTZWxlY3RvcignW3NyYyo9Il9uZXh0Il0nKQ==\"\n\n# Or use stdin with heredoc for multiline scripts:\ncat <<'EOF' | agent-browser eval --stdin\nconst links = document.querySelectorAll('a');\nArray.from(links).map(a => a.href);\nEOF\n```\n\n## State Management\n\n```bash\nagent-browser state save auth.json    # Save cookies, storage, auth state\nagent-browser state load auth.json    # Restore saved state\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 --proxy-bypass <hosts>  # Hosts to bypass proxy\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 --ignore-https-errors   # Ignore SSL certificate errors\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## 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 profiler start              # Start Chrome DevTools profiling\nagent-browser profiler stop trace.json    # Stop and save profile\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=\"browserbase\"         # Cloud browser provider\nAGENT_BROWSER_STREAM_PORT=\"9223\"             # WebSocket streaming port\nAGENT_BROWSER_HOME=\"/path/to/agent-browser\"  # Custom install location\n```\n","isInternal":false,"tokens":2205,"sizeBytes":9825},{"name":"profiling.md","path":".agents/skills/agent-browser/references/profiling.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/agent-browser/references/profiling.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Profiling\n\nCapture Chrome DevTools performance profiles during browser automation for performance analysis.\n\n**Related**: [commands.md](commands.md) for full command reference, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [Basic Profiling](#basic-profiling)\n- [Profiler Commands](#profiler-commands)\n- [Categories](#categories)\n- [Use Cases](#use-cases)\n- [Output Format](#output-format)\n- [Viewing Profiles](#viewing-profiles)\n- [Limitations](#limitations)\n\n## Basic Profiling\n\n```bash\n# Start profiling\nagent-browser profiler start\n\n# Perform actions\nagent-browser navigate https://example.com\nagent-browser click \"#button\"\nagent-browser wait 1000\n\n# Stop and save\nagent-browser profiler stop ./trace.json\n```\n\n## Profiler Commands\n\n```bash\n# Start profiling with default categories\nagent-browser profiler start\n\n# Start with custom trace categories\nagent-browser profiler start --categories \"devtools.timeline,v8.execute,blink.user_timing\"\n\n# Stop profiling and save to file\nagent-browser profiler stop ./trace.json\n```\n\n## Categories\n\nThe `--categories` flag accepts a comma-separated list of Chrome trace categories. Default categories include:\n\n- `devtools.timeline` -- standard DevTools performance traces\n- `v8.execute` -- time spent running JavaScript\n- `blink` -- renderer events\n- `blink.user_timing` -- `performance.mark()` / `performance.measure()` calls\n- `latencyInfo` -- input-to-latency tracking\n- `renderer.scheduler` -- task scheduling and execution\n- `toplevel` -- broad-spectrum basic events\n\nSeveral `disabled-by-default-*` categories are also included for detailed timeline, call stack, and V8 CPU profiling data.\n\n## Use Cases\n\n### Diagnosing Slow Page Loads\n\n```bash\nagent-browser profiler start\nagent-browser navigate https://app.example.com\nagent-browser wait --load networkidle\nagent-browser profiler stop ./page-load-profile.json\n```\n\n### Profiling User Interactions\n\n```bash\nagent-browser navigate https://app.example.com\nagent-browser profiler start\nagent-browser click \"#submit\"\nagent-browser wait 2000\nagent-browser profiler stop ./interaction-profile.json\n```\n\n### CI Performance Regression Checks\n\n```bash\n#!/bin/bash\nagent-browser profiler start\nagent-browser navigate https://app.example.com\nagent-browser wait --load networkidle\nagent-browser profiler stop \"./profiles/build-${BUILD_ID}.json\"\n```\n\n## Output Format\n\nThe output is a JSON file in Chrome Trace Event format:\n\n```json\n{\n  \"traceEvents\": [\n    { \"cat\": \"devtools.timeline\", \"name\": \"RunTask\", \"ph\": \"X\", \"ts\": 12345, \"dur\": 100, ... },\n    ...\n  ],\n  \"metadata\": {\n    \"clock-domain\": \"LINUX_CLOCK_MONOTONIC\"\n  }\n}\n```\n\nThe `metadata.clock-domain` field is set based on the host platform (Linux or macOS). On Windows it is omitted.\n\n## Viewing Profiles\n\nLoad the output JSON file in any of these tools:\n\n- **Chrome DevTools**: Performance panel > Load profile (Ctrl+Shift+I > Performance)\n- **Perfetto UI**: https://ui.perfetto.dev/ -- drag and drop the JSON file\n- **Trace Viewer**: `chrome://tracing` in any Chromium browser\n\n## Limitations\n\n- Only works with Chromium-based browsers (Chrome, Edge). Not supported on Firefox or WebKit.\n- Trace data accumulates in memory while profiling is active (capped at 5 million events). Stop profiling promptly after the area of interest.\n- Data collection on stop has a 30-second timeout. If the browser is unresponsive, the stop command may fail.\n","isInternal":false,"tokens":788,"sizeBytes":3405},{"name":"proxy-support.md","path":".agents/skills/agent-browser/references/proxy-support.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/agent-browser/references/proxy-support.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Proxy Support\n\nProxy configuration for geo-testing, rate limiting avoidance, and corporate environments.\n\n**Related**: [commands.md](commands.md) for global options, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [Basic Proxy Configuration](#basic-proxy-configuration)\n- [Authenticated Proxy](#authenticated-proxy)\n- [SOCKS Proxy](#socks-proxy)\n- [Proxy Bypass](#proxy-bypass)\n- [Common Use Cases](#common-use-cases)\n- [Verifying Proxy Connection](#verifying-proxy-connection)\n- [Troubleshooting](#troubleshooting)\n- [Best Practices](#best-practices)\n\n## Basic Proxy Configuration\n\nUse the `--proxy` flag or set proxy via environment variable:\n\n```bash\n# Via CLI flag\nagent-browser --proxy \"http://proxy.example.com:8080\" open https://example.com\n\n# Via environment variable\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 using `--proxy-bypass` or `NO_PROXY`:\n\n```bash\n# Via CLI flag\nagent-browser --proxy \"http://proxy.example.com:8080\" --proxy-bypass \"localhost,*.internal.com\" open https://example.com\n\n# Via environment variable\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":1248,"sizeBytes":4971},{"name":"session-management.md","path":".agents/skills/agent-browser/references/session-management.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/agent-browser/references/session-management.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Session Management\n\nMultiple isolated browser sessions with state persistence and concurrent browsing.\n\n**Related**: [authentication.md](authentication.md) for login patterns, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [Named Sessions](#named-sessions)\n- [Session Isolation Properties](#session-isolation-properties)\n- [Session State Persistence](#session-state-persistence)\n- [Common Patterns](#common-patterns)\n- [Default Session](#default-session)\n- [Session Cleanup](#session-cleanup)\n- [Best Practices](#best-practices)\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\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":1017,"sizeBytes":4338},{"name":"snapshot-refs.md","path":".agents/skills/agent-browser/references/snapshot-refs.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/agent-browser/references/snapshot-refs.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Snapshot and Refs\n\nCompact element references that reduce context usage dramatically for AI agents.\n\n**Related**: [commands.md](commands.md) for full command reference, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [How Refs Work](#how-refs-work)\n- [Snapshot Command](#the-snapshot-command)\n- [Using Refs](#using-refs)\n- [Ref Lifecycle](#ref-lifecycle)\n- [Best Practices](#best-practices)\n- [Ref Notation Details](#ref-notation-details)\n- [Troubleshooting](#troubleshooting)\n\n## How Refs Work\n\nTraditional approach:\n\n```\nFull DOM/HTML → AI parses → CSS selector → Action (~3000-5000 tokens)\n```\n\nagent-browser approach:\n\n```\nCompact snapshot → @refs assigned → Direct interaction (~200-400 tokens)\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 down to reveal element\nagent-browser scroll down 1000\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":1129,"sizeBytes":4307},{"name":"video-recording.md","path":".agents/skills/agent-browser/references/video-recording.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/agent-browser/references/video-recording.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Video Recording\n\nCapture browser automation as video for debugging, documentation, or verification.\n\n**Related**: [commands.md](commands.md) for full command reference, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [Basic Recording](#basic-recording)\n- [Recording Commands](#recording-commands)\n- [Use Cases](#use-cases)\n- [Best Practices](#best-practices)\n- [Output Format](#output-format)\n- [Limitations](#limitations)\n\n## Basic Recording\n\n```bash\n# Start recording\nagent-browser record start ./demo.webm\n\n# Perform actions\nagent-browser open https://example.com\nagent-browser snapshot -i\nagent-browser click @e1\nagent-browser fill @e2 \"test input\"\n\n# Stop and save\nagent-browser record stop\n```\n\n## Recording Commands\n\n```bash\n# Start recording to file\nagent-browser record start ./output.webm\n\n# Stop current recording\nagent-browser record stop\n\n# Restart with new file (stops current + starts new)\nagent-browser record restart ./take2.webm\n```\n\n## Use Cases\n\n### Debugging Failed Automation\n\n```bash\n#!/bin/bash\n# Record automation for debugging\n\nagent-browser record start ./debug-$(date +%Y%m%d-%H%M%S).webm\n\n# Run your automation\nagent-browser open https://app.example.com\nagent-browser snapshot -i\nagent-browser click @e1 || {\n    echo \"Click failed - check recording\"\n    agent-browser record stop\n    exit 1\n}\n\nagent-browser record stop\n```\n\n### Documentation Generation\n\n```bash\n#!/bin/bash\n# Record workflow for documentation\n\nagent-browser record start ./docs/how-to-login.webm\n\nagent-browser open https://app.example.com/login\nagent-browser wait 1000  # Pause for visibility\n\nagent-browser snapshot -i\nagent-browser fill @e1 \"demo@example.com\"\nagent-browser wait 500\n\nagent-browser fill @e2 \"password\"\nagent-browser wait 500\n\nagent-browser click @e3\nagent-browser wait --load networkidle\nagent-browser wait 1000  # Show result\n\nagent-browser record stop\n```\n\n### CI/CD Test Evidence\n\n```bash\n#!/bin/bash\n# Record E2E test runs for CI artifacts\n\nTEST_NAME=\"${1:-e2e-test}\"\nRECORDING_DIR=\"./test-recordings\"\nmkdir -p \"$RECORDING_DIR\"\n\nagent-browser record start \"$RECORDING_DIR/$TEST_NAME-$(date +%s).webm\"\n\n# Run test\nif run_e2e_test; then\n    echo \"Test passed\"\nelse\n    echo \"Test failed - recording saved\"\nfi\n\nagent-browser record stop\n```\n\n## Best Practices\n\n### 1. Add Pauses for Clarity\n\n```bash\n# Slow down for human viewing\nagent-browser click @e1\nagent-browser wait 500  # Let viewer see result\n```\n\n### 2. Use Descriptive Filenames\n\n```bash\n# Include context in filename\nagent-browser record start ./recordings/login-flow-2024-01-15.webm\nagent-browser record start ./recordings/checkout-test-run-42.webm\n```\n\n### 3. Handle Recording in Error Cases\n\n```bash\n#!/bin/bash\nset -e\n\ncleanup() {\n    agent-browser record stop 2>/dev/null || true\n    agent-browser close 2>/dev/null || true\n}\ntrap cleanup EXIT\n\nagent-browser record start ./automation.webm\n# ... automation steps ...\n```\n\n### 4. Combine with Screenshots\n\n```bash\n# Record video AND capture key frames\nagent-browser record start ./flow.webm\n\nagent-browser open https://example.com\nagent-browser screenshot ./screenshots/step1-homepage.png\n\nagent-browser click @e1\nagent-browser screenshot ./screenshots/step2-after-click.png\n\nagent-browser record stop\n```\n\n## Output Format\n\n- Default format: WebM (VP8/VP9 codec)\n- Compatible with all modern browsers and video players\n- Compressed but high quality\n\n## Limitations\n\n- Recording adds slight overhead to automation\n- Large recordings can consume significant disk space\n- Some headless environments may have codec limitations\n","isInternal":false,"tokens":880,"sizeBytes":3569},{"name":"SKILL.md","path":".agents/skills/agent-browser/SKILL.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/agent-browser/SKILL.md","title":"agent-browser","category":"anthropic-skill","format":"markdown","content":"---\nname: agent-browser\ndescription: Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to \"open a website\", \"fill out a form\", \"click a button\", \"take a screenshot\", \"scrape data from a page\", \"test this web app\", \"login to a site\", \"automate browser actions\", or any task requiring programmatic web interaction.\nallowed-tools: Bash(npx agent-browser:*), Bash(agent-browser:*)\n---\n\n# Browser Automation with agent-browser\n\n## Core Workflow\n\nEvery browser automation follows this pattern:\n\n1. **Navigate**: `agent-browser open <url>`\n2. **Snapshot**: `agent-browser snapshot -i` (get element refs like `@e1`, `@e2`)\n3. **Interact**: Use refs to click, fill, select\n4. **Re-snapshot**: After navigation or DOM changes, get fresh refs\n\n```bash\nagent-browser open https://example.com/form\nagent-browser snapshot -i\n# Output: @e1 [input type=\"email\"], @e2 [input type=\"password\"], @e3 [button] \"Submit\"\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## Command Chaining\n\nCommands can be chained with `&&` in a single shell invocation. The browser persists between commands via a background daemon, so chaining is safe and more efficient than separate calls.\n\n```bash\n# Chain open + wait + snapshot in one call\nagent-browser open https://example.com && agent-browser wait --load networkidle && agent-browser snapshot -i\n\n# Chain multiple interactions\nagent-browser fill @e1 \"user@example.com\" && agent-browser fill @e2 \"password123\" && agent-browser click @e3\n\n# Navigate and capture\nagent-browser open https://example.com && agent-browser wait --load networkidle && agent-browser screenshot page.png\n```\n\n**When to chain:** Use `&&` when you don't need to read the output of an intermediate command before proceeding (e.g., open + wait + screenshot). Run commands separately when you need to parse the output first (e.g., snapshot to discover refs, then interact using those refs).\n\n## Essential Commands\n\n```bash\n# Navigation\nagent-browser open <url>              # Navigate (aliases: goto, navigate)\nagent-browser close                   # Close browser\n\n# Snapshot\nagent-browser snapshot -i             # Interactive elements with refs (recommended)\nagent-browser snapshot -i -C          # Include cursor-interactive elements (divs with onclick, cursor:pointer)\nagent-browser snapshot -s \"#selector\" # Scope to CSS selector\n\n# Interaction (use @refs from snapshot)\nagent-browser click @e1               # Click element\nagent-browser click @e1 --new-tab     # Click and open in new tab\nagent-browser fill @e2 \"text\"         # Clear and type text\nagent-browser type @e2 \"text\"         # Type without clearing\nagent-browser select @e1 \"option\"     # Select dropdown option\nagent-browser check @e1               # Check checkbox\nagent-browser press Enter             # Press key\nagent-browser keyboard type \"text\"    # Type at current focus (no selector)\nagent-browser keyboard inserttext \"text\"  # Insert without key events\nagent-browser scroll down 500         # Scroll page\nagent-browser scroll down 500 --selector \"div.content\"  # Scroll within a specific container\n\n# Get information\nagent-browser get text @e1            # Get element text\nagent-browser get url                 # Get current URL\nagent-browser get title               # Get page title\n\n# Wait\nagent-browser wait @e1                # Wait for element\nagent-browser wait --load networkidle # Wait for network idle\nagent-browser wait --url \"**/page\"    # Wait for URL pattern\nagent-browser wait 2000               # Wait milliseconds\n\n# Downloads\nagent-browser download @e1 ./file.pdf          # Click element to trigger download\nagent-browser wait --download ./output.zip     # Wait for any download to complete\nagent-browser --download-path ./downloads open <url>  # Set default download directory\n\n# Capture\nagent-browser screenshot              # Screenshot to temp dir\nagent-browser screenshot --full       # Full page screenshot\nagent-browser screenshot --annotate   # Annotated screenshot with numbered element labels\nagent-browser pdf output.pdf          # Save as PDF\n\n# Diff (compare page states)\nagent-browser diff snapshot                          # Compare current vs last snapshot\nagent-browser diff snapshot --baseline before.txt    # Compare current vs saved file\nagent-browser diff screenshot --baseline before.png  # Visual pixel diff\nagent-browser diff url <url1> <url2>                 # Compare two pages\nagent-browser diff url <url1> <url2> --wait-until networkidle  # Custom wait strategy\nagent-browser diff url <url1> <url2> --selector \"#main\"  # Scope to element\n```\n\n## Common Patterns\n\n### Form Submission\n\n```bash\nagent-browser open https://example.com/signup\nagent-browser snapshot -i\nagent-browser fill @e1 \"Jane Doe\"\nagent-browser fill @e2 \"jane@example.com\"\nagent-browser select @e3 \"California\"\nagent-browser check @e4\nagent-browser click @e5\nagent-browser wait --load networkidle\n```\n\n### Authentication with Auth Vault (Recommended)\n\n```bash\n# Save credentials once (encrypted with AGENT_BROWSER_ENCRYPTION_KEY)\n# Recommended: pipe password via stdin to avoid shell history exposure\necho \"pass\" | agent-browser auth save github --url https://github.com/login --username user --password-stdin\n\n# Login using saved profile (LLM never sees password)\nagent-browser auth login github\n\n# List/show/delete profiles\nagent-browser auth list\nagent-browser auth show github\nagent-browser auth delete github\n```\n\n### Authentication with State Persistence\n\n```bash\n# Login once and save state\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# Reuse in future sessions\nagent-browser state load auth.json\nagent-browser open https://app.example.com/dashboard\n```\n\n### Session Persistence\n\n```bash\n# Auto-save/restore cookies and localStorage across browser restarts\nagent-browser --session-name myapp open https://app.example.com/login\n# ... login flow ...\nagent-browser close  # State auto-saved to ~/.agent-browser/sessions/\n\n# Next time, state is auto-loaded\nagent-browser --session-name myapp open https://app.example.com/dashboard\n\n# Encrypt state at rest\nexport AGENT_BROWSER_ENCRYPTION_KEY=$(openssl rand -hex 32)\nagent-browser --session-name secure open https://app.example.com\n\n# Manage saved states\nagent-browser state list\nagent-browser state show myapp-default.json\nagent-browser state clear myapp\nagent-browser state clean --older-than 7\n```\n\n### Data Extraction\n\n```bash\nagent-browser open https://example.com/products\nagent-browser snapshot -i\nagent-browser get text @e5           # Get specific element text\nagent-browser get text body > page.txt  # Get all page text\n\n# JSON output for parsing\nagent-browser snapshot -i --json\nagent-browser get text @e1 --json\n```\n\n### Parallel Sessions\n\n```bash\nagent-browser --session site1 open https://site-a.com\nagent-browser --session site2 open https://site-b.com\n\nagent-browser --session site1 snapshot -i\nagent-browser --session site2 snapshot -i\n\nagent-browser session list\n```\n\n### Connect to Existing Chrome\n\n```bash\n# Auto-discover running Chrome with remote debugging enabled\nagent-browser --auto-connect open https://example.com\nagent-browser --auto-connect snapshot\n\n# Or with explicit CDP port\nagent-browser --cdp 9222 snapshot\n```\n\n### Color Scheme (Dark Mode)\n\n```bash\n# Persistent dark mode via flag (applies to all pages and new tabs)\nagent-browser --color-scheme dark open https://example.com\n\n# Or via environment variable\nAGENT_BROWSER_COLOR_SCHEME=dark agent-browser open https://example.com\n\n# Or set during session (persists for subsequent commands)\nagent-browser set media dark\n```\n\n### Visual Browser (Debugging)\n\n```bash\nagent-browser --headed open https://example.com\nagent-browser highlight @e1          # Highlight element\nagent-browser record start demo.webm # Record session\nagent-browser profiler start         # Start Chrome DevTools profiling\nagent-browser profiler stop trace.json # Stop and save profile (path optional)\n```\n\nUse `AGENT_BROWSER_HEADED=1` to enable headed mode via environment variable. Browser extensions work in both headed and headless mode.\n\n### Local Files (PDFs, HTML)\n\n```bash\n# Open local files with file:// URLs\nagent-browser --allow-file-access open file:///path/to/document.pdf\nagent-browser --allow-file-access open file:///path/to/page.html\nagent-browser screenshot output.png\n```\n\n### iOS Simulator (Mobile Safari)\n\n```bash\n# List available iOS simulators\nagent-browser device list\n\n# Launch Safari on a specific device\nagent-browser -p ios --device \"iPhone 16 Pro\" open https://example.com\n\n# Same workflow as desktop - snapshot, interact, re-snapshot\nagent-browser -p ios snapshot -i\nagent-browser -p ios tap @e1          # Tap (alias for click)\nagent-browser -p ios fill @e2 \"text\"\nagent-browser -p ios swipe up         # Mobile-specific gesture\n\n# Take screenshot\nagent-browser -p ios screenshot mobile.png\n\n# Close session (shuts down simulator)\nagent-browser -p ios close\n```\n\n**Requirements:** macOS with Xcode, Appium (`npm install -g appium && appium driver install xcuitest`)\n\n**Real devices:** Works with physical iOS devices if pre-configured. Use `--device \"<UDID>\"` where UDID is from `xcrun xctrace list devices`.\n\n## Security\n\nAll security features are opt-in. By default, agent-browser imposes no restrictions on navigation, actions, or output.\n\n### Content Boundaries (Recommended for AI Agents)\n\nEnable `--content-boundaries` to wrap page-sourced output in markers that help LLMs distinguish tool output from untrusted page content:\n\n```bash\nexport AGENT_BROWSER_CONTENT_BOUNDARIES=1\nagent-browser snapshot\n# Output:\n# --- AGENT_BROWSER_PAGE_CONTENT nonce=<hex> origin=https://example.com ---\n# [accessibility tree]\n# --- END_AGENT_BROWSER_PAGE_CONTENT nonce=<hex> ---\n```\n\n### Domain Allowlist\n\nRestrict navigation to trusted domains. Wildcards like `*.example.com` also match the bare domain `example.com`. Sub-resource requests, WebSocket, and EventSource connections to non-allowed domains are also blocked. Include CDN domains your target pages depend on:\n\n```bash\nexport AGENT_BROWSER_ALLOWED_DOMAINS=\"example.com,*.example.com\"\nagent-browser open https://example.com        # OK\nagent-browser open https://malicious.com       # Blocked\n```\n\n### Action Policy\n\nUse a policy file to gate destructive actions:\n\n```bash\nexport AGENT_BROWSER_ACTION_POLICY=./policy.json\n```\n\nExample `policy.json`:\n\n```json\n{ \"default\": \"deny\", \"allow\": [\"navigate\", \"snapshot\", \"click\", \"scroll\", \"wait\", \"get\"] }\n```\n\nAuth vault operations (`auth login`, etc.) bypass action policy but domain allowlist still applies.\n\n### Output Limits\n\nPrevent context flooding from large pages:\n\n```bash\nexport AGENT_BROWSER_MAX_OUTPUT=50000\n```\n\n## Diffing (Verifying Changes)\n\nUse `diff snapshot` after performing an action to verify it had the intended effect. This compares the current accessibility tree against the last snapshot taken in the session.\n\n```bash\n# Typical workflow: snapshot -> action -> diff\nagent-browser snapshot -i          # Take baseline snapshot\nagent-browser click @e2            # Perform action\nagent-browser diff snapshot        # See what changed (auto-compares to last snapshot)\n```\n\nFor visual regression testing or monitoring:\n\n```bash\n# Save a baseline screenshot, then compare later\nagent-browser screenshot baseline.png\n# ... time passes or changes are made ...\nagent-browser diff screenshot --baseline baseline.png\n\n# Compare staging vs production\nagent-browser diff url https://staging.example.com https://prod.example.com --screenshot\n```\n\n`diff snapshot` output uses `+` for additions and `-` for removals, similar to git diff. `diff screenshot` produces a diff image with changed pixels highlighted in red, plus a mismatch percentage.\n\n## Timeouts and Slow Pages\n\nThe default Playwright timeout is 25 seconds for local browsers. This can be overridden with the `AGENT_BROWSER_DEFAULT_TIMEOUT` environment variable (value in milliseconds). For slow websites or large pages, use explicit waits instead of relying on the default timeout:\n\n```bash\n# Wait for network activity to settle (best for slow pages)\nagent-browser wait --load networkidle\n\n# Wait for a specific element to appear\nagent-browser wait \"#content\"\nagent-browser wait @e1\n\n# Wait for a specific URL pattern (useful after redirects)\nagent-browser wait --url \"**/dashboard\"\n\n# Wait for a JavaScript condition\nagent-browser wait --fn \"document.readyState === 'complete'\"\n\n# Wait a fixed duration (milliseconds) as a last resort\nagent-browser wait 5000\n```\n\nWhen dealing with consistently slow websites, use `wait --load networkidle` after `open` to ensure the page is fully loaded before taking a snapshot. If a specific element is slow to render, wait for it directly with `wait <selector>` or `wait @ref`.\n\n## Session Management and Cleanup\n\nWhen running multiple agents or automations concurrently, always use named sessions to avoid conflicts:\n\n```bash\n# Each agent gets its own isolated session\nagent-browser --session agent1 open site-a.com\nagent-browser --session agent2 open site-b.com\n\n# Check active sessions\nagent-browser session list\n```\n\nAlways close your browser session when done to avoid leaked processes:\n\n```bash\nagent-browser close                    # Close default session\nagent-browser --session agent1 close   # Close specific session\n```\n\nIf a previous session was not closed properly, the daemon may still be running. Use `agent-browser close` to clean it up before starting new work.\n\n## Ref Lifecycle (Important)\n\nRefs (`@e1`, `@e2`, etc.) are invalidated when the page changes. Always re-snapshot after:\n\n- Clicking links or buttons that navigate\n- Form submissions\n- Dynamic content loading (dropdowns, modals)\n\n```bash\nagent-browser click @e5              # Navigates to new page\nagent-browser snapshot -i            # MUST re-snapshot\nagent-browser click @e1              # Use new refs\n```\n\n## Annotated Screenshots (Vision Mode)\n\nUse `--annotate` to take a screenshot with numbered labels overlaid on interactive elements. Each label `[N]` maps to ref `@eN`. This also caches refs, so you can interact with elements immediately without a separate snapshot.\n\n```bash\nagent-browser screenshot --annotate\n# Output includes the image path and a legend:\n#   [1] @e1 button \"Submit\"\n#   [2] @e2 link \"Home\"\n#   [3] @e3 textbox \"Email\"\nagent-browser click @e2              # Click using ref from annotated screenshot\n```\n\nUse annotated screenshots when:\n\n- The page has unlabeled icon buttons or visual-only elements\n- You need to verify visual layout or styling\n- Canvas or chart elements are present (invisible to text snapshots)\n- You need spatial reasoning about element positions\n\n## Semantic Locators (Alternative to Refs)\n\nWhen refs are unavailable or unreliable, use semantic locators:\n\n```bash\nagent-browser find text \"Sign In\" click\nagent-browser find label \"Email\" fill \"user@test.com\"\nagent-browser find role button click --name \"Submit\"\nagent-browser find placeholder \"Search\" type \"query\"\nagent-browser find testid \"submit-btn\" click\n```\n\n## JavaScript Evaluation (eval)\n\nUse `eval` to run JavaScript in the browser context. **Shell quoting can corrupt complex expressions** -- use `--stdin` or `-b` to avoid issues.\n\n```bash\n# Simple expressions work with regular quoting\nagent-browser eval 'document.title'\nagent-browser eval 'document.querySelectorAll(\"img\").length'\n\n# Complex JS: use --stdin with heredoc (RECOMMENDED)\nagent-browser eval --stdin <<'EVALEOF'\nJSON.stringify(\n  Array.from(document.querySelectorAll(\"img\"))\n    .filter(i => !i.alt)\n    .map(i => ({ src: i.src.split(\"/\").pop(), width: i.width }))\n)\nEVALEOF\n\n# Alternative: base64 encoding (avoids all shell escaping issues)\nagent-browser eval -b \"$(echo -n 'Array.from(document.querySelectorAll(\"a\")).map(a => a.href)' | base64)\"\n```\n\n**Why this matters:** When the shell processes your command, inner double quotes, `!` characters (history expansion), backticks, and `$()` can all corrupt the JavaScript before it reaches agent-browser. The `--stdin` and `-b` flags bypass shell interpretation entirely.\n\n**Rules of thumb:**\n\n- Single-line, no nested quotes -> regular `eval 'expression'` with single quotes is fine\n- Nested quotes, arrow functions, template literals, or multiline -> use `eval --stdin <<'EVALEOF'`\n- Programmatic/generated scripts -> use `eval -b` with base64\n\n## Configuration File\n\nCreate `agent-browser.json` in the project root for persistent settings:\n\n```json\n{\n  \"headed\": true,\n  \"proxy\": \"http://localhost:8080\",\n  \"profile\": \"./browser-data\"\n}\n```\n\nPriority (lowest to highest): `~/.agent-browser/config.json` < `./agent-browser.json` < env vars < CLI flags. Use `--config <path>` or `AGENT_BROWSER_CONFIG` env var for a custom config file (exits with error if missing/invalid). All CLI options map to camelCase keys (e.g., `--executable-path` -> `\"executablePath\"`). Boolean flags accept `true`/`false` values (e.g., `--headed false` overrides config). Extensions from user and project configs are merged, not replaced.\n\n## Deep-Dive Documentation\n\n| Reference                                                            | When to Use                                               |\n| -------------------------------------------------------------------- | --------------------------------------------------------- |\n| [references/commands.md](references/commands.md)                     | Full command reference with all options                   |\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/profiling.md](references/profiling.md)                   | Chrome DevTools profiling for performance analysis        |\n| [references/proxy-support.md](references/proxy-support.md)           | Proxy configuration, geo-testing, rotating proxies        |\n\n## Experimental: Native Mode\n\nagent-browser has an experimental native Rust daemon that communicates with Chrome directly via CDP, bypassing Node.js and Playwright entirely. It is opt-in and not recommended for production use yet.\n\n```bash\n# Enable via flag\nagent-browser --native open example.com\n\n# Enable via environment variable (avoids passing --native every time)\nexport AGENT_BROWSER_NATIVE=1\nagent-browser open example.com\n```\n\nThe native daemon supports Chromium and Safari (via WebDriver). Firefox and WebKit are not yet supported. All core commands (navigate, snapshot, click, fill, screenshot, cookies, storage, tabs, eval, etc.) work identically in native mode. Use `agent-browser close` before switching between native and default mode within the same session.\n\n## Ready-to-Use Templates\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\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","frontmatter":{"name":"agent-browser","description":"Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to \"open a website\", \"fill out a form\", \"click a button\", \"take a screenshot\", \"scrape data from a page\", \"test this web app\", \"login to a site\", \"automate browser actions\", or any task requiring programmatic web interaction.","allowed-tools":"Bash(npx agent-browser:*), Bash(agent-browser:*)","allowedTools":["Bash(npx","agent-browser:*),","Bash(agent-browser:*)"]},"allowedTools":["Bash(npx","agent-browser:*),","Bash(agent-browser:*)"],"isInternal":false,"tokens":4398,"sizeBytes":20311},{"name":"ai-gateway.md","path":".agents/skills/ai-sdk/references/ai-gateway.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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":2185},{"name":"common-errors.md","path":".agents/skills/ai-sdk/references/common-errors.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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 <pre key={`${message.id}-${i}`}>{JSON.stringify(part.toolInvocation, null, 2)}</pre>\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 (part.type === 'tool-getWeather' && (part.state === 'input-available' || part.state === 'output-available')) {\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 (part.type === 'tool-getWeather' && (part.state === 'input-available' || part.state === 'output-available')) {\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":2778,"sizeBytes":10915},{"name":"devtools.md","path":".agents/skills/ai-sdk/references/devtools.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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":1196},{"name":"type-safe-agents.md","path":".agents/skills/ai-sdk/references/type-safe-agents.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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({ invocation }: { invocation: WeatherToolInvocation }) {\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":1240,"sizeBytes":5283},{"name":"SKILL.md","path":".agents/skills/ai-sdk/SKILL.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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/bibi/SKILL.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/bibi/SKILL.md","title":"bibi","category":"anthropic-skill","format":"markdown","content":"---\nname: bibi\ndescription: >\n  BibiGPT CLI for summarizing videos, audio, and podcasts directly in the terminal.\n  Use when the user wants to summarize a URL (YouTube, Bilibili, podcast, etc.)\n  or check their BibiGPT authentication status.\n  Requires the BibiGPT desktop app installed with an active login session,\n  or a BIBI_API_TOKEN environment variable set.\n---\n\n# BibiGPT CLI (`bibi`)\n\nSummarize videos, audio, and podcasts from the terminal using the BibiGPT API.\n\n## Installation\n\n### macOS (Homebrew)\n\n```bash\nbrew install --cask jimmylv/bibigpt/bibigpt\n```\n\n### Windows\n\nDownload the installer from: **https://bibigpt.co/download/desktop**\n\n> `winget install BibiGPT` is pending review and not yet available.\n\n### Verify installation\n\n```bash\nbibi --version\n```\n\n## Authentication\n\nAfter installing, the user must log in via the desktop app at least once. The CLI reads the saved session automatically from the desktop app's settings.\n\nAlternatively, set an API token:\n\n```bash\nexport BIBI_API_TOKEN=<token>\n```\n\nOn Windows (PowerShell):\n\n```powershell\n$env:BIBI_API_TOKEN=\"<token>\"\n```\n\n## Commands\n\n### Summarize a URL\n\n**Important**: URLs containing `?` or `&` must be quoted to avoid shell glob errors.\n\n```bash\n# Basic summary (Markdown output to stdout)\nbibi summarize \"<URL>\"\n\n# Async mode — recommended for long videos (>30min)\nbibi summarize \"<URL>\" --async\n\n# Chapter-by-chapter summary\nbibi summarize \"<URL>\" --chapter\n\n# Fetch subtitles/transcript only (no AI summary)\nbibi summarize \"<URL>\" --subtitle\n\n# Full JSON response\nbibi summarize \"<URL>\" --json\n\n# Combine flags\nbibi summarize \"<URL>\" --subtitle --json\n```\n\n**Supported URL types**: YouTube, Bilibili, podcasts, audio files, and any URL supported by BibiGPT.\n\n### Authentication\n\n```bash\n# Check current auth status\nbibi auth check\n\n# Open browser to log in\nbibi auth login\n\n# Show how to set API token\nbibi auth set-token <TOKEN>\n```\n\n### Updates\n\n```bash\n# Check if a new version is available\nbibi check-update\n\n# Download and install the latest version\nbibi self-update\n```\n\n## Output Format\n\n- **Default**: Markdown summary text sent to stdout. Progress info goes to stderr.\n- **--json**: Complete API response as pretty-printed JSON to stdout.\n\nThis means you can pipe the output:\n\n```bash\nbibi summarize \"<URL>\" > summary.md\nbibi summarize \"<URL>\" --json | jq '.summary'\n```\n\n## Error Handling\n\n| Exit Code | Meaning                                        |\n| --------- | ---------------------------------------------- |\n| 0         | Success                                        |\n| 1         | Error (auth missing, API error, timeout, etc.) |\n\n| HTTP Status | User Action                                                      |\n| ----------- | ---------------------------------------------------------------- |\n| 401         | Token expired — run `bibi auth login` or re-login in desktop app |\n| 402/403     | Quota exceeded — visit https://bibigpt.co/pricing                |\n| 429         | Rate limited — wait and retry                                    |\n\n## Usage Tips\n\n- For very long videos, use `--async` to avoid HTTP timeout.\n- Use `--subtitle` to get raw subtitles/transcript without AI summarization.\n- Use `--json` when you need structured data (e.g., `sourceUrl`, `htmlUrl`, `detail`).\n- The `--chapter` flag provides section-by-section summaries, useful for lectures or tutorials.\n- The CLI does NOT open any GUI window — all output goes to the terminal.\n- Run `bibi check-update` periodically to get new features and bug fixes.\n","frontmatter":{"name":"bibi","description":"BibiGPT CLI for summarizing videos, audio, and podcasts directly in the terminal. Use when the user wants to summarize a URL (YouTube, Bilibili, podcast, etc.) or check their BibiGPT authentication status. Requires the BibiGPT desktop app installed with an active login session, or a BIBI_API_TOKEN environment variable set.\n"},"isInternal":false,"tokens":827,"sizeBytes":3548},{"name":"SKILL.md","path":".agents/skills/find-skills/SKILL.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/find-skills/SKILL.md","title":"find-skills","category":"anthropic-skill","format":"markdown","content":"---\nname: find-skills\ndescription: Helps users discover and install agent skills when they ask questions like \"how do I do X\", \"find a skill for X\", \"is there a skill that can...\", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.\n---\n\n# Find Skills\n\nThis skill helps you discover and install skills from the open agent skills ecosystem.\n\n## When to Use This Skill\n\nUse this skill when the user:\n\n- Asks \"how do I do X\" where X might be a common task with an existing skill\n- Says \"find a skill for X\" or \"is there a skill for X\"\n- Asks \"can you do X\" where X is a specialized capability\n- Expresses interest in extending agent capabilities\n- Wants to search for tools, templates, or workflows\n- Mentions they wish they had help with a specific domain (design, testing, deployment, etc.)\n\n## What is the Skills CLI?\n\nThe Skills CLI (`npx skills`) is the package manager for the open agent skills ecosystem. Skills are modular packages that extend agent capabilities with specialized knowledge, workflows, and tools.\n\n**Key commands:**\n\n- `npx skills find [query]` - Search for skills interactively or by keyword\n- `npx skills add <package>` - Install a skill from GitHub or other sources\n- `npx skills check` - Check for skill updates\n- `npx skills update` - Update all installed skills\n\n**Browse skills at:** https://skills.sh/\n\n## How to Help Users Find Skills\n\n### Step 1: Understand What They Need\n\nWhen a user asks for help with something, identify:\n\n1. The domain (e.g., React, testing, design, deployment)\n2. The specific task (e.g., writing tests, creating animations, reviewing PRs)\n3. Whether this is a common enough task that a skill likely exists\n\n### Step 2: Search for Skills\n\nRun the find command with a relevant query:\n\n```bash\nnpx skills find [query]\n```\n\nFor example:\n\n- User asks \"how do I make my React app faster?\" → `npx skills find react performance`\n- User asks \"can you help me with PR reviews?\" → `npx skills find pr review`\n- User asks \"I need to create a changelog\" → `npx skills find changelog`\n\nThe command will return results like:\n\n```\nInstall with npx skills add <owner/repo@skill>\n\nvercel-labs/agent-skills@vercel-react-best-practices\n└ https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices\n```\n\n### Step 3: Present Options to the User\n\nWhen you find relevant skills, present them to the user with:\n\n1. The skill name and what it does\n2. The install command they can run\n3. A link to learn more at skills.sh\n\nExample response:\n\n```\nI found a skill that might help! The \"vercel-react-best-practices\" skill provides\nReact and Next.js performance optimization guidelines from Vercel Engineering.\n\nTo install it:\nnpx skills add vercel-labs/agent-skills@vercel-react-best-practices\n\nLearn more: https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices\n```\n\n### Step 4: Offer to Install\n\nIf the user wants to proceed, you can install the skill for them:\n\n```bash\nnpx skills add <owner/repo@skill> -g -y\n```\n\nThe `-g` flag installs globally (user-level) and `-y` skips confirmation prompts.\n\n## Common Skill Categories\n\nWhen searching, consider these common categories:\n\n| Category        | Example Queries                          |\n| --------------- | ---------------------------------------- |\n| Web Development | react, nextjs, typescript, css, tailwind |\n| Testing         | testing, jest, playwright, e2e           |\n| DevOps          | deploy, docker, kubernetes, ci-cd        |\n| Documentation   | docs, readme, changelog, api-docs        |\n| Code Quality    | review, lint, refactor, best-practices   |\n| Design          | ui, ux, design-system, accessibility     |\n| Productivity    | workflow, automation, git                |\n\n## Tips for Effective Searches\n\n1. **Use specific keywords**: \"react testing\" is better than just \"testing\"\n2. **Try alternative terms**: If \"deploy\" doesn't work, try \"deployment\" or \"ci-cd\"\n3. **Check popular sources**: Many skills come from `vercel-labs/agent-skills` or `ComposioHQ/awesome-claude-skills`\n\n## When No Skills Are Found\n\nIf no relevant skills exist:\n\n1. Acknowledge that no existing skill was found\n2. Offer to help with the task directly using your general capabilities\n3. Suggest the user could create their own skill with `npx skills init`\n\nExample:\n\n```\nI searched for skills related to \"xyz\" but didn't find any matches.\nI can still help you with this task directly! Would you like me to proceed?\n\nIf this is something you do often, you could create your own skill:\nnpx skills init my-xyz-skill\n```\n","frontmatter":{"name":"find-skills","description":"Helps users discover and install agent skills when they ask questions like \"how do I do X\", \"find a skill for X\", \"is there a skill that can...\", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill."},"isInternal":false,"tokens":1110,"sizeBytes":4635},{"name":"async-patterns.md","path":".agents/skills/next-best-practices/async-patterns.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/next-best-practices/async-patterns.md","title":"Next-best-practices Skill","category":"anthropic-skill","format":"markdown","content":"# Async Patterns\n\nIn Next.js 15+, `params`, `searchParams`, `cookies()`, and `headers()` are asynchronous.\n\n## Async Params and SearchParams\n\nAlways type them as `Promise<...>` and await them.\n\n### Pages and Layouts\n\n```tsx\ntype Props = { params: Promise<{ slug: string }> }\n\nexport default async function Page({ params }: Props) {\n  const { slug } = await params\n}\n```\n\n### Route Handlers\n\n```tsx\nexport async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n}\n```\n\n### SearchParams\n\n```tsx\ntype Props = {\n  params: Promise<{ slug: string }>\n  searchParams: Promise<{ query?: string }>\n}\n\nexport default async function Page({ params, searchParams }: Props) {\n  const { slug } = await params\n  const { query } = await searchParams\n}\n```\n\n### Synchronous Components\n\nUse `React.use()` for non-async components:\n\n```tsx\nimport { use } from 'react'\n\ntype Props = { params: Promise<{ slug: string }> }\n\nexport default function Page({ params }: Props) {\n  const { slug } = use(params)\n}\n```\n\n### generateMetadata\n\n```tsx\ntype Props = { params: Promise<{ slug: string }> }\n\nexport async function generateMetadata({ params }: Props): Promise<Metadata> {\n  const { slug } = await params\n  return { title: slug }\n}\n```\n\n## Async Cookies and Headers\n\n```tsx\nimport { cookies, headers } from 'next/headers'\n\nexport default async function Page() {\n  const cookieStore = await cookies()\n  const headersList = await headers()\n\n  const theme = cookieStore.get('theme')\n  const userAgent = headersList.get('user-agent')\n}\n```\n\n## Migration Codemod\n\n```bash\nnpx @next/codemod@latest next-async-request-api .\n```\n","isInternal":false,"tokens":411,"sizeBytes":1658},{"name":"bundling.md","path":".agents/skills/next-best-practices/bundling.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/next-best-practices/bundling.md","title":"Next-best-practices Skill","category":"anthropic-skill","format":"markdown","content":"# Bundling\n\nFix common bundling issues with third-party packages.\n\n## Server-Incompatible Packages\n\nSome packages use browser APIs (`window`, `document`, `localStorage`) and fail in Server Components.\n\n### Error Signs\n\n```\nReferenceError: window is not defined\nReferenceError: document is not defined\nReferenceError: localStorage is not defined\nModule not found: Can't resolve 'fs'\n```\n\n### Solution 1: Mark as Client-Only\n\nIf the package is only needed on client:\n\n```tsx\n// Bad: Fails - package uses window\nimport SomeChart from 'some-chart-library'\n\nexport default function Page() {\n  return <SomeChart />\n}\n\n// Good: Use dynamic import with ssr: false\nimport dynamic from 'next/dynamic'\n\nconst SomeChart = dynamic(() => import('some-chart-library'), {\n  ssr: false,\n})\n\nexport default function Page() {\n  return <SomeChart />\n}\n```\n\n### Solution 2: Externalize from Server Bundle\n\nFor packages that should run on server but have bundling issues:\n\n```js\n// next.config.js\nmodule.exports = {\n  serverExternalPackages: ['problematic-package'],\n}\n```\n\nUse this for:\n\n- Packages with native bindings (sharp, bcrypt)\n- Packages that don't bundle well (some ORMs)\n- Packages with circular dependencies\n\n### Solution 3: Client Component Wrapper\n\nWrap the entire usage in a client component:\n\n```tsx\n// components/ChartWrapper.tsx\n'use client'\n\nimport { Chart } from 'chart-library'\n\nexport function ChartWrapper(props) {\n  return <Chart {...props} />\n}\n\n// app/page.tsx (server component)\nimport { ChartWrapper } from '@/components/ChartWrapper'\n\nexport default function Page() {\n  return <ChartWrapper data={data} />\n}\n```\n\n## CSS Imports\n\nImport CSS files instead of using `<link>` tags. Next.js handles bundling and optimization.\n\n```tsx\n// Bad: Manual link tag\n;<link rel=\"stylesheet\" href=\"/styles.css\" />\n\n// Good: Import CSS\nimport './styles.css'\n\n// Good: CSS Modules\nimport styles from './Button.module.css'\n```\n\n## Polyfills\n\nNext.js includes common polyfills automatically. Don't load redundant ones from polyfill.io or similar CDNs.\n\nAlready included: `Array.from`, `Object.assign`, `Promise`, `fetch`, `Map`, `Set`, `Symbol`, `URLSearchParams`, and 50+ others.\n\n```tsx\n// Bad: Redundant polyfills\n<script src=\"https://polyfill.io/v3/polyfill.min.js?features=fetch,Promise,Array.from\" />\n\n// Good: Next.js includes these automatically\n```\n\n## ESM/CommonJS Issues\n\n### Error Signs\n\n```\nSyntaxError: Cannot use import statement outside a module\nError: require() of ES Module\nModule not found: ESM packages need to be imported\n```\n\n### Solution: Transpile Package\n\n```js\n// next.config.js\nmodule.exports = {\n  transpilePackages: ['some-esm-package', 'another-package'],\n}\n```\n\n## Common Problematic Packages\n\n| Package         | Issue           | Solution                                                        |\n| --------------- | --------------- | --------------------------------------------------------------- |\n| `sharp`         | Native bindings | `serverExternalPackages: ['sharp']`                             |\n| `bcrypt`        | Native bindings | `serverExternalPackages: ['bcrypt']` or use `bcryptjs`          |\n| `canvas`        | Native bindings | `serverExternalPackages: ['canvas']`                            |\n| `recharts`      | Uses window     | `dynamic(() => import('recharts'), { ssr: false })`             |\n| `react-quill`   | Uses document   | `dynamic(() => import('react-quill'), { ssr: false })`          |\n| `mapbox-gl`     | Uses window     | `dynamic(() => import('mapbox-gl'), { ssr: false })`            |\n| `monaco-editor` | Uses window     | `dynamic(() => import('@monaco-editor/react'), { ssr: false })` |\n| `lottie-web`    | Uses document   | `dynamic(() => import('lottie-react'), { ssr: false })`         |\n\n## Bundle Analysis\n\nAnalyze bundle size with the built-in analyzer (Next.js 16.1+):\n\n```bash\nnext experimental-analyze\n```\n\nThis opens an interactive UI to:\n\n- Filter by route, environment (client/server), and type\n- Inspect module sizes and import chains\n- View treemap visualization\n\nSave output for comparison:\n\n```bash\nnext experimental-analyze --output\n# Output saved to .next/diagnostics/analyze\n```\n\nReference: https://nextjs.org/docs/app/guides/package-bundling\n\n## Migrating from Webpack to Turbopack\n\nTurbopack is the default bundler in Next.js 15+. If you have custom webpack config, migrate to Turbopack-compatible alternatives:\n\n```js\n// next.config.js\nmodule.exports = {\n  // Good: Works with Turbopack\n  serverExternalPackages: ['package'],\n  transpilePackages: ['package'],\n\n  // Bad: Webpack-only - migrate away from this\n  webpack: (config) => {\n    // custom webpack config\n  },\n}\n```\n\nReference: https://nextjs.org/docs/app/building-your-application/upgrading/from-webpack-to-turbopack\n","isInternal":false,"tokens":1131,"sizeBytes":4761},{"name":"data-patterns.md","path":".agents/skills/next-best-practices/data-patterns.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/next-best-practices/data-patterns.md","title":"Next-best-practices Skill","category":"anthropic-skill","format":"markdown","content":"# Data Patterns\n\nChoose the right data fetching pattern for each use case.\n\n## Decision Tree\n\n```\nNeed to fetch data?\n├── From a Server Component?\n│   └── Use: Fetch directly (no API needed)\n│\n├── From a Client Component?\n│   ├── Is it a mutation (POST/PUT/DELETE)?\n│   │   └── Use: Server Action\n│   └── Is it a read (GET)?\n│       └── Use: Route Handler OR pass from Server Component\n│\n├── Need external API access (webhooks, third parties)?\n│   └── Use: Route Handler\n│\n└── Need REST API for mobile app / external clients?\n    └── Use: Route Handler\n```\n\n## Pattern 1: Server Components (Preferred for Reads)\n\nFetch data directly in Server Components - no API layer needed.\n\n```tsx\n// app/users/page.tsx\nasync function UsersPage() {\n  // Direct database access - no API round-trip\n  const users = await db.user.findMany()\n\n  // Or fetch from external API\n  const posts = await fetch('https://api.example.com/posts').then((r) => r.json())\n\n  return (\n    <ul>\n      {users.map((user) => (\n        <li key={user.id}>{user.name}</li>\n      ))}\n    </ul>\n  )\n}\n```\n\n**Benefits**:\n\n- No API to maintain\n- No client-server waterfall\n- Secrets stay on server\n- Direct database access\n\n## Pattern 2: Server Actions (Preferred for Mutations)\n\nServer Actions are the recommended way to handle mutations.\n\n```tsx\n// app/actions.ts\n'use server'\n\nimport { revalidatePath } from 'next/cache'\n\nexport async function createPost(formData: FormData) {\n  const title = formData.get('title') as string\n\n  await db.post.create({ data: { title } })\n\n  revalidatePath('/posts')\n}\n\nexport async function deletePost(id: string) {\n  await db.post.delete({ where: { id } })\n\n  revalidateTag('posts')\n}\n```\n\n```tsx\n// app/posts/new/page.tsx\nimport { createPost } from '@/app/actions'\n\nexport default function NewPost() {\n  return (\n    <form action={createPost}>\n      <input name=\"title\" required />\n      <button type=\"submit\">Create</button>\n    </form>\n  )\n}\n```\n\n**Benefits**:\n\n- End-to-end type safety\n- Progressive enhancement (works without JS)\n- Automatic request handling\n- Integrated with React transitions\n\n**Constraints**:\n\n- POST only (no GET caching semantics)\n- Internal use only (no external access)\n- Cannot return non-serializable data\n\n## Pattern 3: Route Handlers (APIs)\n\nUse Route Handlers when you need a REST API.\n\n```tsx\n// app/api/posts/route.ts\nimport { NextRequest, NextResponse } from 'next/server'\n\n// GET is cacheable\nexport async function GET(request: NextRequest) {\n  const posts = await db.post.findMany()\n  return NextResponse.json(posts)\n}\n\n// POST for mutations\nexport async function POST(request: NextRequest) {\n  const body = await request.json()\n  const post = await db.post.create({ data: body })\n  return NextResponse.json(post, { status: 201 })\n}\n```\n\n**When to use**:\n\n- External API access (mobile apps, third parties)\n- Webhooks from external services\n- GET endpoints that need HTTP caching\n- OpenAPI/Swagger documentation needed\n\n**When NOT to use**:\n\n- Internal data fetching (use Server Components)\n- Mutations from your UI (use Server Actions)\n\n## Avoiding Data Waterfalls\n\n### Problem: Sequential Fetches\n\n```tsx\n// Bad: Sequential waterfalls\nasync function Dashboard() {\n  const user = await getUser() // Wait...\n  const posts = await getPosts() // Then wait...\n  const comments = await getComments() // Then wait...\n\n  return <div>...</div>\n}\n```\n\n### Solution 1: Parallel Fetching with Promise.all\n\n```tsx\n// Good: Parallel fetching\nasync function Dashboard() {\n  const [user, posts, comments] = await Promise.all([getUser(), getPosts(), getComments()])\n\n  return <div>...</div>\n}\n```\n\n### Solution 2: Streaming with Suspense\n\n```tsx\n// Good: Show content progressively\nimport { Suspense } from 'react'\n\nasync function Dashboard() {\n  return (\n    <div>\n      <Suspense fallback={<UserSkeleton />}>\n        <UserSection />\n      </Suspense>\n      <Suspense fallback={<PostsSkeleton />}>\n        <PostsSection />\n      </Suspense>\n    </div>\n  )\n}\n\nasync function UserSection() {\n  const user = await getUser() // Fetches independently\n  return <div>{user.name}</div>\n}\n\nasync function PostsSection() {\n  const posts = await getPosts() // Fetches independently\n  return <PostList posts={posts} />\n}\n```\n\n### Solution 3: Preload Pattern\n\n```tsx\n// lib/data.ts\nimport { cache } from 'react'\n\nexport const getUser = cache(async (id: string) => {\n  return db.user.findUnique({ where: { id } })\n})\n\nexport const preloadUser = (id: string) => {\n  void getUser(id) // Fire and forget\n}\n```\n\n```tsx\n// app/user/[id]/page.tsx\nimport { getUser, preloadUser } from '@/lib/data'\n\nexport default async function UserPage({ params }) {\n  const { id } = await params\n\n  // Start fetching early\n  preloadUser(id)\n\n  // Do other work...\n\n  // Data likely ready by now\n  const user = await getUser(id)\n  return <div>{user.name}</div>\n}\n```\n\n## Client Component Data Fetching\n\nWhen Client Components need data:\n\n### Option 1: Pass from Server Component (Preferred)\n\n```tsx\n// Server Component\nasync function Page() {\n  const data = await fetchData()\n  return <ClientComponent initialData={data} />\n}\n\n// Client Component\n;('use client')\nfunction ClientComponent({ initialData }) {\n  const [data, setData] = useState(initialData)\n  // ...\n}\n```\n\n### Option 2: Fetch on Mount (When Necessary)\n\n```tsx\n'use client'\nimport { useEffect, useState } from 'react'\n\nfunction ClientComponent() {\n  const [data, setData] = useState(null)\n\n  useEffect(() => {\n    fetch('/api/data')\n      .then((r) => r.json())\n      .then(setData)\n  }, [])\n\n  if (!data) return <Loading />\n  return <div>{data.value}</div>\n}\n```\n\n### Option 3: Server Action for Reads (Works But Not Ideal)\n\nServer Actions can be called from Client Components for reads, but this is not their intended purpose:\n\n```tsx\n'use client'\nimport { getData } from './actions'\nimport { useEffect, useState } from 'react'\n\nfunction ClientComponent() {\n  const [data, setData] = useState(null)\n\n  useEffect(() => {\n    getData().then(setData)\n  }, [])\n\n  return <div>{data?.value}</div>\n}\n```\n\n**Note**: Server Actions always use POST, so no HTTP caching. Prefer Route Handlers for cacheable reads.\n\n## Quick Reference\n\n| Pattern                | Use Case                    | HTTP Method | Caching              |\n| ---------------------- | --------------------------- | ----------- | -------------------- |\n| Server Component fetch | Internal reads              | Any         | Full Next.js caching |\n| Server Action          | Mutations, form submissions | POST only   | No                   |\n| Route Handler          | External APIs, webhooks     | Any         | GET can be cached    |\n| Client fetch to API    | Client-side reads           | Any         | HTTP cache headers   |\n","isInternal":false,"tokens":1637,"sizeBytes":6823},{"name":"debug-tricks.md","path":".agents/skills/next-best-practices/debug-tricks.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/next-best-practices/debug-tricks.md","title":"Next-best-practices Skill","category":"anthropic-skill","format":"markdown","content":"# Debug Tricks\n\nTricks to speed up debugging Next.js applications.\n\n## MCP Endpoint (Dev Server)\n\nNext.js exposes a `/_next/mcp` endpoint in development for AI-assisted debugging via MCP (Model Context Protocol).\n\n- **Next.js 16+**: Enabled by default, use `next-devtools-mcp`\n- **Next.js < 16**: Requires `experimental.mcpServer: true` in next.config.js\n\nReference: https://nextjs.org/docs/app/guides/mcp\n\n**Important**: Find the actual port of the running Next.js dev server (check terminal output or `package.json` scripts). Don't assume port 3000.\n\n### Request Format\n\nThe endpoint uses JSON-RPC 2.0 over HTTP POST:\n\n```bash\ncurl -X POST http://localhost:<port>/_next/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json, text/event-stream\" \\\n  -d '{\n    \"jsonrpc\": \"2.0\",\n    \"id\": \"1\",\n    \"method\": \"tools/call\",\n    \"params\": {\n      \"name\": \"<tool-name>\",\n      \"arguments\": {}\n    }\n  }'\n```\n\n### Available Tools\n\n#### `get_errors`\n\nGet current errors from dev server (build errors, runtime errors with source-mapped stacks):\n\n```json\n{ \"name\": \"get_errors\", \"arguments\": {} }\n```\n\n#### `get_routes`\n\nDiscover all routes by scanning filesystem:\n\n```json\n{ \"name\": \"get_routes\", \"arguments\": {} }\n// Optional: { \"name\": \"get_routes\", \"arguments\": { \"routerType\": \"app\" } }\n```\n\nReturns: `{ \"appRouter\": [\"/\", \"/api/users/[id]\", ...], \"pagesRouter\": [...] }`\n\n#### `get_project_metadata`\n\nGet project path and dev server URL:\n\n```json\n{ \"name\": \"get_project_metadata\", \"arguments\": {} }\n```\n\nReturns: `{ \"projectPath\": \"/path/to/project\", \"devServerUrl\": \"http://localhost:3000\" }`\n\n#### `get_page_metadata`\n\nGet runtime metadata about current page render (requires active browser session):\n\n```json\n{ \"name\": \"get_page_metadata\", \"arguments\": {} }\n```\n\nReturns segment trie data showing layouts, boundaries, and page components.\n\n#### `get_logs`\n\nGet path to Next.js development log file:\n\n```json\n{ \"name\": \"get_logs\", \"arguments\": {} }\n```\n\nReturns path to `<distDir>/logs/next-development.log`\n\n#### `get_server_action_by_id`\n\nLocate a Server Action by ID:\n\n```json\n{ \"name\": \"get_server_action_by_id\", \"arguments\": { \"actionId\": \"<action-id>\" } }\n```\n\n### Example: Get Errors\n\n```bash\ncurl -X POST http://localhost:<port>/_next/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json, text/event-stream\" \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"tools/call\",\"params\":{\"name\":\"get_errors\",\"arguments\":{}}}'\n```\n\n## Rebuild Specific Routes (Next.js 16+)\n\nUse `--debug-build-paths` to rebuild only specific routes instead of the entire app:\n\n```bash\n# Rebuild a specific route\nnext build --debug-build-paths \"/dashboard\"\n\n# Rebuild routes matching a glob\nnext build --debug-build-paths \"/api/*\"\n\n# Dynamic routes\nnext build --debug-build-paths \"/blog/[slug]\"\n```\n\nUse this to:\n\n- Quickly verify a build fix without full rebuild\n- Debug static generation issues for specific pages\n- Iterate faster on build errors\n","isInternal":false,"tokens":781,"sizeBytes":2970},{"name":"directives.md","path":".agents/skills/next-best-practices/directives.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/next-best-practices/directives.md","title":"Next-best-practices Skill","category":"anthropic-skill","format":"markdown","content":"# Directives\n\n## React Directives\n\nThese are React directives, not Next.js specific.\n\n### `'use client'`\n\nMarks a component as a Client Component. Required for:\n\n- React hooks (`useState`, `useEffect`, etc.)\n- Event handlers (`onClick`, `onChange`)\n- Browser APIs (`window`, `localStorage`)\n\n```tsx\n'use client'\n\nimport { useState } from 'react'\n\nexport function Counter() {\n  const [count, setCount] = useState(0)\n  return <button onClick={() => setCount(count + 1)}>{count}</button>\n}\n```\n\nReference: https://react.dev/reference/rsc/use-client\n\n### `'use server'`\n\nMarks a function as a Server Action. Can be passed to Client Components.\n\n```tsx\n'use server'\n\nexport async function submitForm(formData: FormData) {\n  // Runs on server\n}\n```\n\nOr inline within a Server Component:\n\n```tsx\nexport default function Page() {\n  async function submit() {\n    'use server'\n    // Runs on server\n  }\n  return <form action={submit}>...</form>\n}\n```\n\nReference: https://react.dev/reference/rsc/use-server\n\n---\n\n## Next.js Directive\n\n### `'use cache'`\n\nMarks a function or component for caching. Part of Next.js Cache Components.\n\n```tsx\n'use cache'\n\nexport async function getCachedData() {\n  return await fetchData()\n}\n```\n\nRequires `cacheComponents: true` in `next.config.ts`.\n\nFor detailed usage including cache profiles, `cacheLife()`, `cacheTag()`, and `updateTag()`, see the `next-cache-components` skill.\n\nReference: https://nextjs.org/docs/app/api-reference/directives/use-cache\n","isInternal":false,"tokens":351,"sizeBytes":1477},{"name":"error-handling.md","path":".agents/skills/next-best-practices/error-handling.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/next-best-practices/error-handling.md","title":"Next-best-practices Skill","category":"anthropic-skill","format":"markdown","content":"# Error Handling\n\nHandle errors gracefully in Next.js applications.\n\nReference: https://nextjs.org/docs/app/getting-started/error-handling\n\n## Error Boundaries\n\n### `error.tsx`\n\nCatches errors in a route segment and its children:\n\n```tsx\n'use client'\n\nexport default function Error({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {\n  return (\n    <div>\n      <h2>Something went wrong!</h2>\n      <button onClick={() => reset()}>Try again</button>\n    </div>\n  )\n}\n```\n\n**Important:** `error.tsx` must be a Client Component.\n\n### `global-error.tsx`\n\nCatches errors in root layout:\n\n```tsx\n'use client'\n\nexport default function GlobalError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {\n  return (\n    <html>\n      <body>\n        <h2>Something went wrong!</h2>\n        <button onClick={() => reset()}>Try again</button>\n      </body>\n    </html>\n  )\n}\n```\n\n**Important:** Must include `<html>` and `<body>` tags.\n\n## Server Actions: Navigation API Gotcha\n\n**Do NOT wrap navigation APIs in try-catch.** They throw special errors that Next.js handles internally.\n\nReference: https://nextjs.org/docs/app/api-reference/functions/redirect#behavior\n\n```tsx\n'use server'\n\nimport { redirect } from 'next/navigation'\nimport { notFound } from 'next/navigation'\n\n// Bad: try-catch catches the navigation \"error\"\nasync function createPost(formData: FormData) {\n  try {\n    const post = await db.post.create({ ... })\n    redirect(`/posts/${post.id}`)  // This throws!\n  } catch (error) {\n    // redirect() throw is caught here - navigation fails!\n    return { error: 'Failed to create post' }\n  }\n}\n\n// Good: Call navigation APIs outside try-catch\nasync function createPost(formData: FormData) {\n  let post\n  try {\n    post = await db.post.create({ ... })\n  } catch (error) {\n    return { error: 'Failed to create post' }\n  }\n  redirect(`/posts/${post.id}`)  // Outside try-catch\n}\n\n// Good: Re-throw navigation errors\nasync function createPost(formData: FormData) {\n  try {\n    const post = await db.post.create({ ... })\n    redirect(`/posts/${post.id}`)\n  } catch (error) {\n    if (error instanceof Error && error.message === 'NEXT_REDIRECT') {\n      throw error  // Re-throw navigation errors\n    }\n    return { error: 'Failed to create post' }\n  }\n}\n```\n\nSame applies to:\n\n- `redirect()` - 307 temporary redirect\n- `permanentRedirect()` - 308 permanent redirect\n- `notFound()` - 404 not found\n- `forbidden()` - 403 forbidden\n- `unauthorized()` - 401 unauthorized\n\nUse `unstable_rethrow()` to re-throw these errors in catch blocks:\n\n```tsx\nimport { unstable_rethrow } from 'next/navigation'\n\nasync function action() {\n  try {\n    // ...\n    redirect('/success')\n  } catch (error) {\n    unstable_rethrow(error) // Re-throws Next.js internal errors\n    return { error: 'Something went wrong' }\n  }\n}\n```\n\n## Redirects\n\n```tsx\nimport { redirect, permanentRedirect } from 'next/navigation'\n\n// 307 Temporary - use for most cases\nredirect('/new-path')\n\n// 308 Permanent - use for URL migrations (cached by browsers)\npermanentRedirect('/new-url')\n```\n\n## Auth Errors\n\nTrigger auth-related error pages:\n\n```tsx\nimport { forbidden, unauthorized } from 'next/navigation'\n\nasync function Page() {\n  const session = await getSession()\n\n  if (!session) {\n    unauthorized() // Renders unauthorized.tsx (401)\n  }\n\n  if (!session.hasAccess) {\n    forbidden() // Renders forbidden.tsx (403)\n  }\n\n  return <Dashboard />\n}\n```\n\nCreate corresponding error pages:\n\n```tsx\n// app/forbidden.tsx\nexport default function Forbidden() {\n  return <div>You don't have access to this resource</div>\n}\n\n// app/unauthorized.tsx\nexport default function Unauthorized() {\n  return <div>Please log in to continue</div>\n}\n```\n\n## Not Found\n\n### `not-found.tsx`\n\nCustom 404 page for a route segment:\n\n```tsx\nexport default function NotFound() {\n  return (\n    <div>\n      <h2>Not Found</h2>\n      <p>Could not find the requested resource</p>\n    </div>\n  )\n}\n```\n\n### Triggering Not Found\n\n```tsx\nimport { notFound } from 'next/navigation'\n\nexport default async function Page({ params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n  const post = await getPost(id)\n\n  if (!post) {\n    notFound() // Renders closest not-found.tsx\n  }\n\n  return <div>{post.title}</div>\n}\n```\n\n## Error Hierarchy\n\nErrors bubble up to the nearest error boundary:\n\n```\napp/\n├── error.tsx           # Catches errors from all children\n├── blog/\n│   ├── error.tsx       # Catches errors in /blog/*\n│   └── [slug]/\n│       ├── error.tsx   # Catches errors in /blog/[slug]\n│       └── page.tsx\n└── layout.tsx          # Errors here go to global-error.tsx\n```\n","isInternal":false,"tokens":1185,"sizeBytes":4720},{"name":"file-conventions.md","path":".agents/skills/next-best-practices/file-conventions.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/next-best-practices/file-conventions.md","title":"Next-best-practices Skill","category":"anthropic-skill","format":"markdown","content":"# File Conventions\n\nNext.js App Router uses file-based routing with special file conventions.\n\n## Project Structure\n\nReference: https://nextjs.org/docs/app/getting-started/project-structure\n\n```\napp/\n├── layout.tsx          # Root layout (required)\n├── page.tsx            # Home page (/)\n├── loading.tsx         # Loading UI\n├── error.tsx           # Error UI\n├── not-found.tsx       # 404 UI\n├── global-error.tsx    # Global error UI\n├── route.ts            # API endpoint\n├── template.tsx        # Re-rendered layout\n├── default.tsx         # Parallel route fallback\n├── blog/\n│   ├── page.tsx        # /blog\n│   └── [slug]/\n│       └── page.tsx    # /blog/:slug\n└── (group)/            # Route group (no URL impact)\n    └── page.tsx\n```\n\n## Special Files\n\n| File            | Purpose                                  |\n| --------------- | ---------------------------------------- |\n| `page.tsx`      | UI for a route segment                   |\n| `layout.tsx`    | Shared UI for segment and children       |\n| `loading.tsx`   | Loading UI (Suspense boundary)           |\n| `error.tsx`     | Error UI (Error boundary)                |\n| `not-found.tsx` | 404 UI                                   |\n| `route.ts`      | API endpoint                             |\n| `template.tsx`  | Like layout but re-renders on navigation |\n| `default.tsx`   | Fallback for parallel routes             |\n\n## Route Segments\n\n```\napp/\n├── blog/               # Static segment: /blog\n├── [slug]/             # Dynamic segment: /:slug\n├── [...slug]/          # Catch-all: /a/b/c\n├── [[...slug]]/        # Optional catch-all: / or /a/b/c\n└── (marketing)/        # Route group (ignored in URL)\n```\n\n## Parallel Routes\n\n```\napp/\n├── @analytics/\n│   └── page.tsx\n├── @sidebar/\n│   └── page.tsx\n└── layout.tsx          # Receives { analytics, sidebar } as props\n```\n\n## Intercepting Routes\n\n```\napp/\n├── feed/\n│   └── page.tsx\n├── @modal/\n│   └── (.)photo/[id]/  # Intercepts /photo/[id] from /feed\n│       └── page.tsx\n└── photo/[id]/\n    └── page.tsx\n```\n\nConventions:\n\n- `(.)` - same level\n- `(..)` - one level up\n- `(..)(..)` - two levels up\n- `(...)` - from root\n\n## Private Folders\n\n```\napp/\n├── _components/        # Private folder (not a route)\n│   └── Button.tsx\n└── page.tsx\n```\n\nPrefix with `_` to exclude from routing.\n\n## Middleware / Proxy\n\n### Next.js 14-15: `middleware.ts`\n\n```ts\n// middleware.ts (root of project)\nimport { NextResponse } from 'next/server'\nimport type { NextRequest } from 'next/server'\n\nexport function middleware(request: NextRequest) {\n  // Auth, redirects, rewrites, etc.\n  return NextResponse.next()\n}\n\nexport const config = {\n  matcher: ['/dashboard/:path*', '/api/:path*'],\n}\n```\n\n### Next.js 16+: `proxy.ts`\n\nRenamed for clarity - same capabilities, different names:\n\n```ts\n// proxy.ts (root of project)\nimport { NextResponse } from 'next/server'\nimport type { NextRequest } from 'next/server'\n\nexport function proxy(request: NextRequest) {\n  // Same logic as middleware\n  return NextResponse.next()\n}\n\nexport const proxyConfig = {\n  matcher: ['/dashboard/:path*', '/api/:path*'],\n}\n```\n\n| Version | File            | Export         | Config        |\n| ------- | --------------- | -------------- | ------------- |\n| v14-15  | `middleware.ts` | `middleware()` | `config`      |\n| v16+    | `proxy.ts`      | `proxy()`      | `proxyConfig` |\n\n**Migration**: Run `npx @next/codemod@latest upgrade` to auto-rename.\n\n## File Conventions Reference\n\nReference: https://nextjs.org/docs/app/api-reference/file-conventions\n","isInternal":false,"tokens":979,"sizeBytes":3761},{"name":"font.md","path":".agents/skills/next-best-practices/font.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/next-best-practices/font.md","title":"Next-best-practices Skill","category":"anthropic-skill","format":"markdown","content":"# Font Optimization\n\nUse `next/font` for automatic font optimization with zero layout shift.\n\n## Google Fonts\n\n```tsx\n// app/layout.tsx\nimport { Inter } from 'next/font/google'\n\nconst inter = Inter({ subsets: ['latin'] })\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\" className={inter.className}>\n      <body>{children}</body>\n    </html>\n  )\n}\n```\n\n## Multiple Fonts\n\n```tsx\nimport { Inter, Roboto_Mono } from 'next/font/google'\n\nconst inter = Inter({\n  subsets: ['latin'],\n  variable: '--font-inter',\n})\n\nconst robotoMono = Roboto_Mono({\n  subsets: ['latin'],\n  variable: '--font-roboto-mono',\n})\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\" className={`${inter.variable} ${robotoMono.variable}`}>\n      <body>{children}</body>\n    </html>\n  )\n}\n```\n\nUse in CSS:\n\n```css\nbody {\n  font-family: var(--font-inter);\n}\n\ncode {\n  font-family: var(--font-roboto-mono);\n}\n```\n\n## Font Weights and Styles\n\n```tsx\n// Single weight\nconst inter = Inter({\n  subsets: ['latin'],\n  weight: '400',\n})\n\n// Multiple weights\nconst inter = Inter({\n  subsets: ['latin'],\n  weight: ['400', '500', '700'],\n})\n\n// Variable font (recommended) - includes all weights\nconst inter = Inter({\n  subsets: ['latin'],\n  // No weight needed - variable fonts support all weights\n})\n\n// With italic\nconst inter = Inter({\n  subsets: ['latin'],\n  style: ['normal', 'italic'],\n})\n```\n\n## Local Fonts\n\n```tsx\nimport localFont from 'next/font/local'\n\nconst myFont = localFont({\n  src: './fonts/MyFont.woff2',\n})\n\n// Multiple files for different weights\nconst myFont = localFont({\n  src: [\n    {\n      path: './fonts/MyFont-Regular.woff2',\n      weight: '400',\n      style: 'normal',\n    },\n    {\n      path: './fonts/MyFont-Bold.woff2',\n      weight: '700',\n      style: 'normal',\n    },\n  ],\n})\n\n// Variable font\nconst myFont = localFont({\n  src: './fonts/MyFont-Variable.woff2',\n  variable: '--font-my-font',\n})\n```\n\n## Tailwind CSS Integration\n\n```tsx\n// app/layout.tsx\nimport { Inter } from 'next/font/google'\n\nconst inter = Inter({\n  subsets: ['latin'],\n  variable: '--font-inter',\n})\n\nexport default function RootLayout({ children }) {\n  return (\n    <html lang=\"en\" className={inter.variable}>\n      <body>{children}</body>\n    </html>\n  )\n}\n```\n\n```js\n// tailwind.config.js\nmodule.exports = {\n  theme: {\n    extend: {\n      fontFamily: {\n        sans: ['var(--font-inter)'],\n      },\n    },\n  },\n}\n```\n\n## Preloading Subsets\n\nOnly load needed character subsets:\n\n```tsx\n// Latin only (most common)\nconst inter = Inter({ subsets: ['latin'] })\n\n// Multiple subsets\nconst inter = Inter({ subsets: ['latin', 'latin-ext', 'cyrillic'] })\n```\n\n## Display Strategy\n\nControl font loading behavior:\n\n```tsx\nconst inter = Inter({\n  subsets: ['latin'],\n  display: 'swap', // Default - shows fallback, swaps when loaded\n})\n\n// Options:\n// 'auto' - browser decides\n// 'block' - short block period, then swap\n// 'swap' - immediate fallback, swap when ready (recommended)\n// 'fallback' - short block, short swap, then fallback\n// 'optional' - short block, no swap (use if font is optional)\n```\n\n## Don't Use Manual Font Links\n\nAlways use `next/font` instead of `<link>` tags for Google Fonts.\n\n```tsx\n// Bad: Manual link tag (blocks rendering, no optimization)\n<link href=\"https://fonts.googleapis.com/css2?family=Inter\" rel=\"stylesheet\" />\n\n// Bad: Missing display and preconnect\n<link href=\"https://fonts.googleapis.com/css2?family=Inter\" rel=\"stylesheet\" />\n\n// Good: Use next/font (self-hosted, zero layout shift)\nimport { Inter } from 'next/font/google'\n\nconst inter = Inter({ subsets: ['latin'] })\n```\n\n## Common Mistakes\n\n```tsx\n// Bad: Importing font in every component\n// components/Button.tsx\nimport { Inter } from 'next/font/google'\nconst inter = Inter({ subsets: ['latin'] }) // Creates new instance each time!\n\n// Good: Import once in layout, use CSS variable\n// app/layout.tsx\nconst inter = Inter({ subsets: ['latin'], variable: '--font-inter' })\n\n// Bad: Using @import in CSS (blocks rendering)\n/* globals.css */\n@import url('https://fonts.googleapis.com/css2?family=Inter');\n\n// Good: Use next/font (self-hosted, no network request)\nimport { Inter } from 'next/font/google'\n\n// Bad: Loading all weights when only using a few\nconst inter = Inter({ subsets: ['latin'] }) // Loads all weights\n\n// Good: Specify only needed weights (for non-variable fonts)\nconst inter = Inter({ subsets: ['latin'], weight: ['400', '700'] })\n\n// Bad: Missing subset - loads all characters\nconst inter = Inter({})\n\n// Good: Always specify subset\nconst inter = Inter({ subsets: ['latin'] })\n```\n\n## Font in Specific Components\n\n```tsx\n// For component-specific fonts, export from a shared file\n// lib/fonts.ts\nimport { Inter, Playfair_Display } from 'next/font/google'\n\nexport const inter = Inter({ subsets: ['latin'], variable: '--font-inter' })\nexport const playfair = Playfair_Display({ subsets: ['latin'], variable: '--font-playfair' })\n\n// components/Heading.tsx\nimport { playfair } from '@/lib/fonts'\n\nexport function Heading({ children }) {\n  return <h1 className={playfair.className}>{children}</h1>\n}\n```\n","isInternal":false,"tokens":1316,"sizeBytes":5197},{"name":"functions.md","path":".agents/skills/next-best-practices/functions.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/next-best-practices/functions.md","title":"Next-best-practices Skill","category":"anthropic-skill","format":"markdown","content":"# Functions\n\nNext.js function APIs.\n\nReference: https://nextjs.org/docs/app/api-reference/functions\n\n## Navigation Hooks (Client)\n\n| Hook                        | Purpose                                                        | Reference                                                                                |\n| --------------------------- | -------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |\n| `useRouter`                 | Programmatic navigation (`push`, `replace`, `back`, `refresh`) | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-router)                   |\n| `usePathname`               | Get current pathname                                           | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-pathname)                 |\n| `useSearchParams`           | Read URL search parameters                                     | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-search-params)            |\n| `useParams`                 | Access dynamic route parameters                                | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-params)                   |\n| `useSelectedLayoutSegment`  | Active child segment (one level)                               | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-selected-layout-segment)  |\n| `useSelectedLayoutSegments` | All active segments below layout                               | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-selected-layout-segments) |\n| `useLinkStatus`             | Check link prefetch status                                     | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-link-status)              |\n| `useReportWebVitals`        | Report Core Web Vitals metrics                                 | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-report-web-vitals)        |\n\n## Server Functions\n\n| Function     | Purpose                                      | Reference                                                              |\n| ------------ | -------------------------------------------- | ---------------------------------------------------------------------- |\n| `cookies`    | Read/write cookies                           | [Docs](https://nextjs.org/docs/app/api-reference/functions/cookies)    |\n| `headers`    | Read request headers                         | [Docs](https://nextjs.org/docs/app/api-reference/functions/headers)    |\n| `draftMode`  | Enable preview of unpublished CMS content    | [Docs](https://nextjs.org/docs/app/api-reference/functions/draft-mode) |\n| `after`      | Run code after response finishes streaming   | [Docs](https://nextjs.org/docs/app/api-reference/functions/after)      |\n| `connection` | Wait for connection before dynamic rendering | [Docs](https://nextjs.org/docs/app/api-reference/functions/connection) |\n| `userAgent`  | Parse User-Agent header                      | [Docs](https://nextjs.org/docs/app/api-reference/functions/userAgent)  |\n\n## Generate Functions\n\n| Function                | Purpose                                 | Reference                                                                           |\n| ----------------------- | --------------------------------------- | ----------------------------------------------------------------------------------- |\n| `generateStaticParams`  | Pre-render dynamic routes at build time | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-static-params)  |\n| `generateMetadata`      | Dynamic metadata                        | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-metadata)       |\n| `generateViewport`      | Dynamic viewport config                 | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-viewport)       |\n| `generateSitemaps`      | Multiple sitemaps for large sites       | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-sitemaps)       |\n| `generateImageMetadata` | Multiple OG images per route            | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-image-metadata) |\n\n## Request/Response\n\n| Function        | Purpose                        | Reference                                                                  |\n| --------------- | ------------------------------ | -------------------------------------------------------------------------- |\n| `NextRequest`   | Extended Request with helpers  | [Docs](https://nextjs.org/docs/app/api-reference/functions/next-request)   |\n| `NextResponse`  | Extended Response with helpers | [Docs](https://nextjs.org/docs/app/api-reference/functions/next-response)  |\n| `ImageResponse` | Generate OG images             | [Docs](https://nextjs.org/docs/app/api-reference/functions/image-response) |\n\n## Common Examples\n\n### Navigation\n\nUse `next/link` for internal navigation instead of `<a>` tags.\n\n```tsx\n// Bad: Plain anchor tag\n;<a href=\"/about\">About</a>\n\n// Good: Next.js Link\nimport Link from 'next/link'\n;<Link href=\"/about\">About</Link>\n```\n\nActive link styling:\n\n```tsx\n'use client'\n\nimport Link from 'next/link'\nimport { usePathname } from 'next/navigation'\n\nexport function NavLink({ href, children }) {\n  const pathname = usePathname()\n\n  return (\n    <Link href={href} className={pathname === href ? 'active' : ''}>\n      {children}\n    </Link>\n  )\n}\n```\n\n### Static Generation\n\n```tsx\n// app/blog/[slug]/page.tsx\nexport async function generateStaticParams() {\n  const posts = await getPosts()\n  return posts.map((post) => ({ slug: post.slug }))\n}\n```\n\n### After Response\n\n```tsx\nimport { after } from 'next/server'\n\nexport async function POST(request: Request) {\n  const data = await processRequest(request)\n\n  after(async () => {\n    await logAnalytics(data)\n  })\n\n  return Response.json({ success: true })\n}\n```\n","isInternal":false,"tokens":1105,"sizeBytes":5947},{"name":"hydration-error.md","path":".agents/skills/next-best-practices/hydration-error.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/next-best-practices/hydration-error.md","title":"Next-best-practices Skill","category":"anthropic-skill","format":"markdown","content":"# Hydration Errors\n\nDiagnose and fix React hydration mismatch errors.\n\n## Error Signs\n\n- \"Hydration failed because the initial UI does not match\"\n- \"Text content does not match server-rendered HTML\"\n\n## Debugging\n\nIn development, click the hydration error to see the server/client diff.\n\n## Common Causes and Fixes\n\n### Browser-only APIs\n\n```tsx\n// Bad: Causes mismatch - window doesn't exist on server\n;<div>{window.innerWidth}</div>\n\n// Good: Use client component with mounted check\n;('use client')\nimport { useState, useEffect } from 'react'\n\nexport function ClientOnly({ children }: { children: React.ReactNode }) {\n  const [mounted, setMounted] = useState(false)\n  useEffect(() => setMounted(true), [])\n  return mounted ? children : null\n}\n```\n\n### Date/Time Rendering\n\nServer and client may be in different timezones:\n\n```tsx\n// Bad: Causes mismatch\n;<span>{new Date().toLocaleString()}</span>\n\n// Good: Render on client only\n;('use client')\nconst [time, setTime] = useState<string>()\nuseEffect(() => setTime(new Date().toLocaleString()), [])\n```\n\n### Random Values or IDs\n\n```tsx\n// Bad: Random values differ between server and client\n<div id={Math.random().toString()}>\n\n// Good: Use useId hook\nimport { useId } from 'react'\n\nfunction Input() {\n  const id = useId()\n  return <input id={id} />\n}\n```\n\n### Invalid HTML Nesting\n\n```tsx\n// Bad: Invalid - div inside p\n<p><div>Content</div></p>\n\n// Bad: Invalid - p inside p\n<p><p>Nested</p></p>\n\n// Good: Valid nesting\n<div><p>Content</p></div>\n```\n\n### Third-party Scripts\n\nScripts that modify DOM during hydration.\n\n```tsx\n// Good: Use next/script with afterInteractive\nimport Script from 'next/script'\n\nexport default function Page() {\n  return <Script src=\"https://example.com/script.js\" strategy=\"afterInteractive\" />\n}\n```\n","isInternal":false,"tokens":435,"sizeBytes":1783},{"name":"image.md","path":".agents/skills/next-best-practices/image.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/next-best-practices/image.md","title":"Next-best-practices Skill","category":"anthropic-skill","format":"markdown","content":"# Image Optimization\n\nUse `next/image` for automatic image optimization.\n\n## Always Use next/image\n\n```tsx\n// Bad: Avoid native img\n;<img src=\"/hero.png\" alt=\"Hero\" />\n\n// Good: Use next/image\nimport Image from 'next/image'\n;<Image src=\"/hero.png\" alt=\"Hero\" width={800} height={400} />\n```\n\n## Required Props\n\nImages need explicit dimensions to prevent layout shift:\n\n```tsx\n// Local images - dimensions inferred automatically\nimport heroImage from './hero.png'\n<Image src={heroImage} alt=\"Hero\" />\n\n// Remote images - must specify width/height\n<Image src=\"https://example.com/image.jpg\" alt=\"Hero\" width={800} height={400} />\n\n// Or use fill for parent-relative sizing\n<div style={{ position: 'relative', width: '100%', height: 400 }}>\n  <Image src=\"/hero.png\" alt=\"Hero\" fill style={{ objectFit: 'cover' }} />\n</div>\n```\n\n## Remote Images Configuration\n\nRemote domains must be configured in `next.config.js`:\n\n```js\n// next.config.js\nmodule.exports = {\n  images: {\n    remotePatterns: [\n      {\n        protocol: 'https',\n        hostname: 'example.com',\n        pathname: '/images/**',\n      },\n      {\n        protocol: 'https',\n        hostname: '*.cdn.com', // Wildcard subdomain\n      },\n    ],\n  },\n}\n```\n\n## Responsive Images\n\nUse `sizes` to tell the browser which size to download:\n\n```tsx\n// Full-width hero\n<Image\n  src=\"/hero.png\"\n  alt=\"Hero\"\n  fill\n  sizes=\"100vw\"\n/>\n\n// Responsive grid (3 columns on desktop, 1 on mobile)\n<Image\n  src=\"/card.png\"\n  alt=\"Card\"\n  fill\n  sizes=\"(max-width: 768px) 100vw, 33vw\"\n/>\n\n// Fixed sidebar image\n<Image\n  src=\"/avatar.png\"\n  alt=\"Avatar\"\n  width={200}\n  height={200}\n  sizes=\"200px\"\n/>\n```\n\n## Blur Placeholder\n\nPrevent layout shift with placeholders:\n\n```tsx\n// Local images - automatic blur hash\nimport heroImage from './hero.png'\n<Image src={heroImage} alt=\"Hero\" placeholder=\"blur\" />\n\n// Remote images - provide blurDataURL\n<Image\n  src=\"https://example.com/image.jpg\"\n  alt=\"Hero\"\n  width={800}\n  height={400}\n  placeholder=\"blur\"\n  blurDataURL=\"data:image/jpeg;base64,/9j/4AAQSkZJRg...\"\n/>\n\n// Or use color placeholder\n<Image\n  src=\"https://example.com/image.jpg\"\n  alt=\"Hero\"\n  width={800}\n  height={400}\n  placeholder=\"empty\"\n  style={{ backgroundColor: '#e0e0e0' }}\n/>\n```\n\n## Priority Loading\n\nUse `priority` for above-the-fold images (LCP):\n\n```tsx\n// Hero image - loads immediately\n<Image src=\"/hero.png\" alt=\"Hero\" fill priority />\n\n// Below-fold images - lazy loaded by default (no priority needed)\n<Image src=\"/card.png\" alt=\"Card\" width={400} height={300} />\n```\n\n## Common Mistakes\n\n```tsx\n// Bad: Missing sizes with fill - downloads largest image\n<Image src=\"/hero.png\" alt=\"Hero\" fill />\n\n// Good: Add sizes for proper responsive behavior\n<Image src=\"/hero.png\" alt=\"Hero\" fill sizes=\"100vw\" />\n\n// Bad: Using width/height for aspect ratio only\n<Image src=\"/hero.png\" alt=\"Hero\" width={16} height={9} />\n\n// Good: Use actual display dimensions or fill with sizes\n<Image src=\"/hero.png\" alt=\"Hero\" fill sizes=\"100vw\" style={{ objectFit: 'cover' }} />\n\n// Bad: Remote image without config\n<Image src=\"https://untrusted.com/image.jpg\" alt=\"Image\" width={400} height={300} />\n// Error: Invalid src prop, hostname not configured\n\n// Good: Add hostname to next.config.js remotePatterns\n```\n\n## Static Export\n\nWhen using `output: 'export'`, use `unoptimized` or custom loader:\n\n```tsx\n// Option 1: Disable optimization\n;<Image src=\"/hero.png\" alt=\"Hero\" width={800} height={400} unoptimized />\n\n// Option 2: Global config\n// next.config.js\nmodule.exports = {\n  output: 'export',\n  images: { unoptimized: true },\n}\n\n// Option 3: Custom loader (Cloudinary, Imgix, etc.)\nconst cloudinaryLoader = ({ src, width, quality }) => {\n  return `https://res.cloudinary.com/demo/image/upload/w_${width},q_${quality || 75}/${src}`\n}\n\n;<Image loader={cloudinaryLoader} src=\"sample.jpg\" alt=\"Sample\" width={800} height={400} />\n```\n","isInternal":false,"tokens":1029,"sizeBytes":3886},{"name":"metadata.md","path":".agents/skills/next-best-practices/metadata.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/next-best-practices/metadata.md","title":"Next-best-practices Skill","category":"anthropic-skill","format":"markdown","content":"# Metadata\n\nAdd SEO metadata to Next.js pages using the Metadata API.\n\n## Important: Server Components Only\n\nThe `metadata` object and `generateMetadata` function are **only supported in Server Components**. They cannot be used in Client Components.\n\nIf the target page has `'use client'`:\n\n1. Remove `'use client'` if possible, move client logic to child components\n2. Or extract metadata to a parent Server Component layout\n3. Or split the file: Server Component with metadata imports Client Components\n\n## Static Metadata\n\n```tsx\nimport type { Metadata } from 'next'\n\nexport const metadata: Metadata = {\n  title: 'Page Title',\n  description: 'Page description for search engines',\n}\n```\n\n## Dynamic Metadata\n\n```tsx\nimport type { Metadata } from 'next'\n\ntype Props = { params: Promise<{ slug: string }> }\n\nexport async function generateMetadata({ params }: Props): Promise<Metadata> {\n  const { slug } = await params\n  const post = await getPost(slug)\n  return { title: post.title, description: post.description }\n}\n```\n\n## Avoid Duplicate Fetches\n\nUse React `cache()` when the same data is needed for both metadata and page:\n\n```tsx\nimport { cache } from 'react'\n\nexport const getPost = cache(async (slug: string) => {\n  return await db.posts.findFirst({ where: { slug } })\n})\n```\n\n## Viewport\n\nSeparate from metadata for streaming support:\n\n```tsx\nimport type { Viewport } from 'next'\n\nexport const viewport: Viewport = {\n  width: 'device-width',\n  initialScale: 1,\n  themeColor: '#000000',\n}\n\n// Or dynamic\nexport function generateViewport({ params }): Viewport {\n  return { themeColor: getThemeColor(params) }\n}\n```\n\n## Title Templates\n\nIn root layout for consistent naming:\n\n```tsx\nexport const metadata: Metadata = {\n  title: { default: 'Site Name', template: '%s | Site Name' },\n}\n```\n\n## Metadata File Conventions\n\nReference: https://nextjs.org/docs/app/getting-started/project-structure#metadata-file-conventions\n\nPlace these files in `app/` directory (or route segments):\n\n| File                            | Purpose                                       |\n| ------------------------------- | --------------------------------------------- |\n| `favicon.ico`                   | Favicon                                       |\n| `icon.png` / `icon.svg`         | App icon                                      |\n| `apple-icon.png`                | Apple app icon                                |\n| `opengraph-image.png`           | OG image                                      |\n| `twitter-image.png`             | Twitter card image                            |\n| `sitemap.ts` / `sitemap.xml`    | Sitemap (use `generateSitemaps` for multiple) |\n| `robots.ts` / `robots.txt`      | Robots directives                             |\n| `manifest.ts` / `manifest.json` | Web app manifest                              |\n\n## SEO Best Practice: Static Files Are Often Enough\n\nFor most sites, **static metadata files provide excellent SEO coverage**:\n\n```\napp/\n├── favicon.ico\n├── opengraph-image.png     # Works for both OG and Twitter\n├── sitemap.ts\n├── robots.ts\n└── layout.tsx              # With title/description metadata\n```\n\n**Tips:**\n\n- A single `opengraph-image.png` covers both Open Graph and Twitter (Twitter falls back to OG)\n- Static `title` and `description` in layout metadata is sufficient for most pages\n- Only use dynamic `generateMetadata` when content varies per page\n\n---\n\n# OG Image Generation\n\nGenerate dynamic Open Graph images using `next/og`.\n\n## Important Rules\n\n1. **Use `next/og`** - not `@vercel/og` (it's built into Next.js)\n2. **No searchParams** - OG images can't access search params, use route params instead\n3. **Avoid Edge runtime** - Use default Node.js runtime\n\n```tsx\n// Good\nimport { ImageResponse } from 'next/og'\n\n// Bad\n// import { ImageResponse } from '@vercel/og'\n// export const runtime = 'edge'\n```\n\n## Basic OG Image\n\n```tsx\n// app/opengraph-image.tsx\nimport { ImageResponse } from 'next/og'\n\nexport const alt = 'Site Name'\nexport const size = { width: 1200, height: 630 }\nexport const contentType = 'image/png'\n\nexport default function Image() {\n  return new ImageResponse(\n    (\n      <div\n        style={{\n          fontSize: 128,\n          background: 'white',\n          width: '100%',\n          height: '100%',\n          display: 'flex',\n          alignItems: 'center',\n          justifyContent: 'center',\n        }}\n      >\n        Hello World\n      </div>\n    ),\n    { ...size },\n  )\n}\n```\n\n## Dynamic OG Image\n\n```tsx\n// app/blog/[slug]/opengraph-image.tsx\nimport { ImageResponse } from 'next/og'\n\nexport const alt = 'Blog Post'\nexport const size = { width: 1200, height: 630 }\nexport const contentType = 'image/png'\n\ntype Props = { params: Promise<{ slug: string }> }\n\nexport default async function Image({ params }: Props) {\n  const { slug } = await params\n  const post = await getPost(slug)\n\n  return new ImageResponse(\n    (\n      <div\n        style={{\n          fontSize: 48,\n          background: 'linear-gradient(to bottom, #1a1a1a, #333)',\n          color: 'white',\n          width: '100%',\n          height: '100%',\n          display: 'flex',\n          flexDirection: 'column',\n          alignItems: 'center',\n          justifyContent: 'center',\n          padding: 48,\n        }}\n      >\n        <div style={{ fontSize: 64, fontWeight: 'bold' }}>{post.title}</div>\n        <div style={{ marginTop: 24, opacity: 0.8 }}>{post.description}</div>\n      </div>\n    ),\n    { ...size },\n  )\n}\n```\n\n## Custom Fonts\n\n```tsx\nimport { ImageResponse } from 'next/og'\nimport { join } from 'path'\nimport { readFile } from 'fs/promises'\n\nexport default async function Image() {\n  const fontPath = join(process.cwd(), 'assets/fonts/Inter-Bold.ttf')\n  const fontData = await readFile(fontPath)\n\n  return new ImageResponse(<div style={{ fontFamily: 'Inter', fontSize: 64 }}>Custom Font Text</div>, {\n    width: 1200,\n    height: 630,\n    fonts: [{ name: 'Inter', data: fontData, style: 'normal' }],\n  })\n}\n```\n\n## File Naming\n\n- `opengraph-image.tsx` - Open Graph (Facebook, LinkedIn)\n- `twitter-image.tsx` - Twitter/X cards (optional, falls back to OG)\n\n## Styling Notes\n\nImageResponse uses Flexbox layout:\n\n- Use `display: 'flex'`\n- No CSS Grid support\n- Styles must be inline objects\n\n## Multiple OG Images\n\nUse `generateImageMetadata` for multiple images per route:\n\n```tsx\n// app/blog/[slug]/opengraph-image.tsx\nimport { ImageResponse } from 'next/og'\n\nexport async function generateImageMetadata({ params }) {\n  const images = await getPostImages(params.slug)\n  return images.map((img, idx) => ({\n    id: idx,\n    alt: img.alt,\n    size: { width: 1200, height: 630 },\n    contentType: 'image/png',\n  }))\n}\n\nexport default async function Image({ params, id }) {\n  const images = await getPostImages(params.slug)\n  const image = images[id]\n  return new ImageResponse(/* ... */)\n}\n```\n\n## Multiple Sitemaps\n\nUse `generateSitemaps` for large sites:\n\n```tsx\n// app/sitemap.ts\nimport type { MetadataRoute } from 'next'\n\nexport async function generateSitemaps() {\n  // Return array of sitemap IDs\n  return [{ id: 0 }, { id: 1 }, { id: 2 }]\n}\n\nexport default async function sitemap({ id }: { id: number }): Promise<MetadataRoute.Sitemap> {\n  const start = id * 50000\n  const end = start + 50000\n  const products = await getProducts(start, end)\n\n  return products.map((product) => ({\n    url: `https://example.com/product/${product.id}`,\n    lastModified: product.updatedAt,\n  }))\n}\n```\n\nGenerates `/sitemap/0.xml`, `/sitemap/1.xml`, etc.\n","isInternal":false,"tokens":1812,"sizeBytes":7518},{"name":"parallel-routes.md","path":".agents/skills/next-best-practices/parallel-routes.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/next-best-practices/parallel-routes.md","title":"Next-best-practices Skill","category":"anthropic-skill","format":"markdown","content":"# Parallel & Intercepting Routes\n\nParallel routes render multiple pages in the same layout. Intercepting routes show a different UI when navigating from within your app vs direct URL access. Together they enable modal patterns.\n\n## File Structure\n\n```\napp/\n├── @modal/                    # Parallel route slot\n│   ├── default.tsx            # Required! Returns null\n│   ├── (.)photos/             # Intercepts /photos/*\n│   │   └── [id]/\n│   │       └── page.tsx       # Modal content\n│   └── [...]catchall/         # Optional: catch unmatched\n│       └── page.tsx\n├── photos/\n│   └── [id]/\n│       └── page.tsx           # Full page (direct access)\n├── layout.tsx                 # Renders both children and @modal\n└── page.tsx\n```\n\n## Step 1: Root Layout with Slot\n\n```tsx\n// app/layout.tsx\nexport default function RootLayout({ children, modal }: { children: React.ReactNode; modal: React.ReactNode }) {\n  return (\n    <html>\n      <body>\n        {children}\n        {modal}\n      </body>\n    </html>\n  )\n}\n```\n\n## Step 2: Default File (Critical!)\n\n**Every parallel route slot MUST have a `default.tsx`** to prevent 404s on hard navigation.\n\n```tsx\n// app/@modal/default.tsx\nexport default function Default() {\n  return null\n}\n```\n\nWithout this file, refreshing any page will 404 because Next.js can't determine what to render in the `@modal` slot.\n\n## Step 3: Intercepting Route (Modal)\n\nThe `(.)` prefix intercepts routes at the same level.\n\n```tsx\n// app/@modal/(.)photos/[id]/page.tsx\nimport { Modal } from '@/components/modal'\n\nexport default async function PhotoModal({ params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n  const photo = await getPhoto(id)\n\n  return (\n    <Modal>\n      <img src={photo.url} alt={photo.title} />\n    </Modal>\n  )\n}\n```\n\n## Step 4: Full Page (Direct Access)\n\n```tsx\n// app/photos/[id]/page.tsx\nexport default async function PhotoPage({ params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n  const photo = await getPhoto(id)\n\n  return (\n    <div className=\"full-page\">\n      <img src={photo.url} alt={photo.title} />\n      <h1>{photo.title}</h1>\n    </div>\n  )\n}\n```\n\n## Step 5: Modal Component with Correct Closing\n\n**Critical: Use `router.back()` to close modals, NOT `router.push()` or `<Link>`.**\n\n```tsx\n// components/modal.tsx\n'use client'\n\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useRef } from 'react'\n\nexport function Modal({ children }: { children: React.ReactNode }) {\n  const router = useRouter()\n  const overlayRef = useRef<HTMLDivElement>(null)\n\n  // Close on escape key\n  useEffect(() => {\n    function onKeyDown(e: KeyboardEvent) {\n      if (e.key === 'Escape') {\n        router.back() // Correct\n      }\n    }\n    document.addEventListener('keydown', onKeyDown)\n    return () => document.removeEventListener('keydown', onKeyDown)\n  }, [router])\n\n  // Close on overlay click\n  const handleOverlayClick = useCallback(\n    (e: React.MouseEvent) => {\n      if (e.target === overlayRef.current) {\n        router.back() // Correct\n      }\n    },\n    [router],\n  )\n\n  return (\n    <div\n      ref={overlayRef}\n      onClick={handleOverlayClick}\n      className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\"\n    >\n      <div className=\"mx-4 w-full max-w-2xl rounded-lg bg-white p-6\">\n        <button\n          onClick={() => router.back()} // Correct!\n          className=\"absolute top-4 right-4\"\n        >\n          Close\n        </button>\n        {children}\n      </div>\n    </div>\n  )\n}\n```\n\n### Why NOT `router.push('/')` or `<Link href=\"/\">`?\n\nUsing `push` or `Link` to \"close\" a modal:\n\n1. Adds a new history entry (back button shows modal again)\n2. Doesn't properly clear the intercepted route\n3. Can cause the modal to flash or persist unexpectedly\n\n`router.back()` correctly:\n\n1. Removes the intercepted route from history\n2. Returns to the previous page\n3. Properly unmounts the modal\n\n## Route Matcher Reference\n\nMatchers match **route segments**, not filesystem paths:\n\n| Matcher    | Matches       | Example                                                               |\n| ---------- | ------------- | --------------------------------------------------------------------- |\n| `(.)`      | Same level    | `@modal/(.)photos` intercepts `/photos`                               |\n| `(..)`     | One level up  | `@modal/(..)settings` from `/dashboard/@modal` intercepts `/settings` |\n| `(..)(..)` | Two levels up | Rarely used                                                           |\n| `(...)`    | From root     | `@modal/(...)photos` intercepts `/photos` from anywhere               |\n\n**Common mistake**: Thinking `(..)` means \"parent folder\" - it means \"parent route segment\".\n\n## Handling Hard Navigation\n\nWhen users directly visit `/photos/123` (bookmark, refresh, shared link):\n\n- The intercepting route is bypassed\n- The full `photos/[id]/page.tsx` renders\n- Modal doesn't appear (expected behavior)\n\nIf you want the modal to appear on direct access too, you need additional logic:\n\n```tsx\n// app/photos/[id]/page.tsx\nimport { Modal } from '@/components/modal'\n\nexport default async function PhotoPage({ params }) {\n  const { id } = await params\n  const photo = await getPhoto(id)\n\n  // Option: Render as modal on direct access too\n  return (\n    <Modal>\n      <img src={photo.url} alt={photo.title} />\n    </Modal>\n  )\n}\n```\n\n## Common Gotchas\n\n### 1. Missing `default.tsx` → 404 on Refresh\n\nEvery `@slot` folder needs a `default.tsx` that returns `null` (or appropriate content).\n\n### 2. Modal Persists After Navigation\n\nYou're using `router.push()` instead of `router.back()`.\n\n### 3. Nested Parallel Routes Need Defaults Too\n\nIf you have `@modal` inside a route group, each level needs its own `default.tsx`:\n\n```\napp/\n├── (marketing)/\n│   ├── @modal/\n│   │   └── default.tsx     # Needed!\n│   └── layout.tsx\n└── layout.tsx\n```\n\n### 4. Intercepted Route Shows Wrong Content\n\nCheck your matcher:\n\n- `(.)photos` intercepts `/photos` from the same route level\n- If your `@modal` is in `app/dashboard/@modal`, use `(.)photos` to intercept `/dashboard/photos`, not `/photos`\n\n### 5. TypeScript Errors with `params`\n\nIn Next.js 15+, `params` is a Promise:\n\n```tsx\n// Correct\nexport default async function Page({ params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n}\n```\n\n## Complete Example: Photo Gallery Modal\n\n```\napp/\n├── @modal/\n│   ├── default.tsx\n│   └── (.)photos/\n│       └── [id]/\n│           └── page.tsx\n├── photos/\n│   ├── page.tsx           # Gallery grid\n│   └── [id]/\n│       └── page.tsx       # Full photo page\n├── layout.tsx\n└── page.tsx\n```\n\nLinks in the gallery:\n\n```tsx\n// app/photos/page.tsx\nimport Link from 'next/link'\n\nexport default async function Gallery() {\n  const photos = await getPhotos()\n\n  return (\n    <div className=\"grid grid-cols-3 gap-4\">\n      {photos.map((photo) => (\n        <Link key={photo.id} href={`/photos/${photo.id}`}>\n          <img src={photo.thumbnail} alt={photo.title} />\n        </Link>\n      ))}\n    </div>\n  )\n}\n```\n\nClicking a photo → Modal opens (intercepted)\nDirect URL → Full page renders\nRefresh while modal open → Full page renders\n","isInternal":false,"tokens":1834,"sizeBytes":7415},{"name":"route-handlers.md","path":".agents/skills/next-best-practices/route-handlers.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/next-best-practices/route-handlers.md","title":"Next-best-practices Skill","category":"anthropic-skill","format":"markdown","content":"# Route Handlers\n\nCreate API endpoints with `route.ts` files.\n\n## Basic Usage\n\n```tsx\n// app/api/users/route.ts\nexport async function GET() {\n  const users = await getUsers()\n  return Response.json(users)\n}\n\nexport async function POST(request: Request) {\n  const body = await request.json()\n  const user = await createUser(body)\n  return Response.json(user, { status: 201 })\n}\n```\n\n## Supported Methods\n\n`GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`\n\n## GET Handler Conflicts with page.tsx\n\n**A `route.ts` and `page.tsx` cannot coexist in the same folder.**\n\n```\napp/\n├── api/\n│   └── users/\n│       └── route.ts    # /api/users\n└── users/\n    ├── page.tsx        # /users (page)\n    └── route.ts        # Warning: Conflicts with page.tsx!\n```\n\nIf you need both a page and an API at the same path, use different paths:\n\n```\napp/\n├── users/\n│   └── page.tsx        # /users (page)\n└── api/\n    └── users/\n        └── route.ts    # /api/users (API)\n```\n\n## Environment Behavior\n\nRoute handlers run in a **Server Component-like environment**:\n\n- Yes: Can use `async/await`\n- Yes: Can access `cookies()`, `headers()`\n- Yes: Can use Node.js APIs\n- No: Cannot use React hooks\n- No: Cannot use React DOM APIs\n- No: Cannot use browser APIs\n\n```tsx\n// Bad: This won't work - no React DOM in route handlers\nimport { renderToString } from 'react-dom/server'\n\nexport async function GET() {\n  const html = renderToString(<Component />) // Error!\n  return new Response(html)\n}\n```\n\n## Dynamic Route Handlers\n\n```tsx\n// app/api/users/[id]/route.ts\nexport async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n  const user = await getUser(id)\n\n  if (!user) {\n    return Response.json({ error: 'Not found' }, { status: 404 })\n  }\n\n  return Response.json(user)\n}\n```\n\n## Request Helpers\n\n```tsx\nexport async function GET(request: Request) {\n  // URL and search params\n  const { searchParams } = new URL(request.url)\n  const query = searchParams.get('q')\n\n  // Headers\n  const authHeader = request.headers.get('authorization')\n\n  // Cookies (Next.js helper)\n  const cookieStore = await cookies()\n  const token = cookieStore.get('token')\n\n  return Response.json({ query, token })\n}\n```\n\n## Response Helpers\n\n```tsx\n// JSON response\nreturn Response.json({ data })\n\n// With status\nreturn Response.json({ error: 'Not found' }, { status: 404 })\n\n// With headers\nreturn Response.json(data, {\n  headers: {\n    'Cache-Control': 'max-age=3600',\n  },\n})\n\n// Redirect\nreturn Response.redirect(new URL('/login', request.url))\n\n// Stream\nreturn new Response(stream, {\n  headers: { 'Content-Type': 'text/event-stream' },\n})\n```\n\n## When to Use Route Handlers vs Server Actions\n\n| Use Case                 | Route Handlers | Server Actions |\n| ------------------------ | -------------- | -------------- |\n| Form submissions         | No             | Yes            |\n| Data mutations from UI   | No             | Yes            |\n| Third-party webhooks     | Yes            | No             |\n| External API consumption | Yes            | No             |\n| Public REST API          | Yes            | No             |\n| File uploads             | Both work      | Both work      |\n\n**Prefer Server Actions** for mutations triggered from your UI.\n**Use Route Handlers** for external integrations and public APIs.\n","isInternal":false,"tokens":836,"sizeBytes":3429},{"name":"rsc-boundaries.md","path":".agents/skills/next-best-practices/rsc-boundaries.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/next-best-practices/rsc-boundaries.md","title":"Next-best-practices Skill","category":"anthropic-skill","format":"markdown","content":"# RSC Boundaries\n\nDetect and prevent invalid patterns when crossing Server/Client component boundaries.\n\n## Detection Rules\n\n### 1. Async Client Components Are Invalid\n\nClient components **cannot** be async functions. Only Server Components can be async.\n\n**Detect:** File has `'use client'` AND component is `async function` or returns `Promise`\n\n```tsx\n// Bad: async client component\n'use client'\nexport default async function UserProfile() {\n  const user = await getUser() // Cannot await in client component\n  return <div>{user.name}</div>\n}\n\n// Good: Remove async, fetch data in parent server component\n// page.tsx (server component - no 'use client')\nexport default async function Page() {\n  const user = await getUser()\n  return <UserProfile user={user} />\n}\n\n// UserProfile.tsx (client component)\n;('use client')\nexport function UserProfile({ user }: { user: User }) {\n  return <div>{user.name}</div>\n}\n```\n\n```tsx\n// Bad: async arrow function client component\n'use client'\nconst Dashboard = async () => {\n  const data = await fetchDashboard()\n  return <div>{data}</div>\n}\n\n// Good: Fetch in server component, pass data down\n```\n\n### 2. Non-Serializable Props to Client Components\n\nProps passed from Server → Client must be JSON-serializable.\n\n**Detect:** Server component passes these to a client component:\n\n- Functions (except Server Actions with `'use server'`)\n- `Date` objects\n- `Map`, `Set`, `WeakMap`, `WeakSet`\n- Class instances\n- `Symbol` (unless globally registered)\n- Circular references\n\n```tsx\n// Bad: Function prop\n// page.tsx (server)\nexport default function Page() {\n  const handleClick = () => console.log('clicked')\n  return <ClientButton onClick={handleClick} />\n}\n\n// Good: Define function inside client component\n// ClientButton.tsx\n;('use client')\nexport function ClientButton() {\n  const handleClick = () => console.log('clicked')\n  return <button onClick={handleClick}>Click</button>\n}\n```\n\n```tsx\n// Bad: Date object (silently becomes string, then crashes)\n// page.tsx (server)\nexport default async function Page() {\n  const post = await getPost()\n  return <PostCard createdAt={post.createdAt} /> // Date object\n}\n\n// PostCard.tsx (client) - will crash on .getFullYear()\n;('use client')\nexport function PostCard({ createdAt }: { createdAt: Date }) {\n  return <span>{createdAt.getFullYear()}</span> // Runtime error!\n}\n\n// Good: Serialize to string on server\n// page.tsx (server)\nexport default async function Page() {\n  const post = await getPost()\n  return <PostCard createdAt={post.createdAt.toISOString()} />\n}\n\n// PostCard.tsx (client)\n;('use client')\nexport function PostCard({ createdAt }: { createdAt: string }) {\n  const date = new Date(createdAt)\n  return <span>{date.getFullYear()}</span>\n}\n```\n\n```tsx\n// Bad: Class instance\nconst user = new UserModel(data)\n<ClientProfile user={user} /> // Methods will be stripped\n\n// Good: Pass plain object\nconst user = await getUser()\n<ClientProfile user={{ id: user.id, name: user.name }} />\n```\n\n```tsx\n// Bad: Map/Set\n<ClientComponent items={new Map([['a', 1]])} />\n\n// Good: Convert to array/object\n<ClientComponent items={Object.fromEntries(map)} />\n<ClientComponent items={Array.from(set)} />\n```\n\n### 3. Server Actions Are the Exception\n\nFunctions marked with `'use server'` CAN be passed to client components.\n\n```tsx\n// Valid: Server Action can be passed\n// actions.ts\n'use server'\nexport async function submitForm(formData: FormData) {\n  // server-side logic\n}\n\n// page.tsx (server)\nimport { submitForm } from './actions'\nexport default function Page() {\n  return <ClientForm onSubmit={submitForm} /> // OK!\n}\n\n// ClientForm.tsx (client)\n;('use client')\nexport function ClientForm({ onSubmit }: { onSubmit: (data: FormData) => Promise<void> }) {\n  return <form action={onSubmit}>...</form>\n}\n```\n\n## Quick Reference\n\n| Pattern                           | Valid? | Fix                                   |\n| --------------------------------- | ------ | ------------------------------------- |\n| `'use client'` + `async function` | No     | Fetch in server parent, pass data     |\n| Pass `() => {}` to client         | No     | Define in client or use server action |\n| Pass `new Date()` to client       | No     | Use `.toISOString()`                  |\n| Pass `new Map()` to client        | No     | Convert to object/array               |\n| Pass class instance to client     | No     | Pass plain object                     |\n| Pass server action to client      | Yes    | -                                     |\n| Pass `string/number/boolean`      | Yes    | -                                     |\n| Pass plain object/array           | Yes    | -                                     |\n","isInternal":false,"tokens":1065,"sizeBytes":4682},{"name":"runtime-selection.md","path":".agents/skills/next-best-practices/runtime-selection.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/next-best-practices/runtime-selection.md","title":"Next-best-practices Skill","category":"anthropic-skill","format":"markdown","content":"# Runtime Selection\n\n## Use Node.js Runtime by Default\n\nUse the default Node.js runtime for new routes and pages. Only use Edge runtime if the project already uses it or there's a specific requirement.\n\n```tsx\n// Good: Default - no runtime config needed (uses Node.js)\nexport default function Page() { ... }\n\n// Caution: Only if already used in project or specifically required\nexport const runtime = 'edge'\n```\n\n## When to Use Each\n\n### Node.js Runtime (Default)\n\n- Full Node.js API support\n- File system access (`fs`)\n- Full `crypto` support\n- Database connections\n- Most npm packages work\n\n### Edge Runtime\n\n- Only for specific edge-location latency requirements\n- Limited API (no `fs`, limited `crypto`)\n- Smaller cold start\n- Geographic distribution needs\n\n## Detection\n\n**Before adding `runtime = 'edge'`**, check:\n\n1. Does the project already use Edge runtime?\n2. Is there a specific latency requirement?\n3. Are all dependencies Edge-compatible?\n\nIf unsure, use Node.js runtime.\n","isInternal":false,"tokens":220,"sizeBytes":986},{"name":"scripts.md","path":".agents/skills/next-best-practices/scripts.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/next-best-practices/scripts.md","title":"Next-best-practices Skill","category":"anthropic-skill","format":"markdown","content":"# Scripts\n\nLoading third-party scripts in Next.js.\n\n## Use next/script\n\nAlways use `next/script` instead of native `<script>` tags for better performance.\n\n```tsx\n// Bad: Native script tag\n;<script src=\"https://example.com/script.js\"></script>\n\n// Good: Next.js Script component\nimport Script from 'next/script'\n;<Script src=\"https://example.com/script.js\" />\n```\n\n## Inline Scripts Need ID\n\nInline scripts require an `id` attribute for Next.js to track them.\n\n```tsx\n// Bad: Missing id\n<Script dangerouslySetInnerHTML={{ __html: 'console.log(\"hi\")' }} />\n\n// Good: Has id\n<Script id=\"my-script\" dangerouslySetInnerHTML={{ __html: 'console.log(\"hi\")' }} />\n\n// Good: Inline with id\n<Script id=\"show-banner\">\n  {`document.getElementById('banner').classList.remove('hidden')`}\n</Script>\n```\n\n## Don't Put Script in Head\n\n`next/script` should not be placed inside `next/head`. It handles its own positioning.\n\n```tsx\n// Bad: Script inside Head\nimport Head from 'next/head'\nimport Script from 'next/script'\n\n<Head>\n  <Script src=\"/analytics.js\" />\n</Head>\n\n// Good: Script outside Head\n<Head>\n  <title>Page</title>\n</Head>\n<Script src=\"/analytics.js\" />\n```\n\n## Loading Strategies\n\n```tsx\n// afterInteractive (default) - Load after page is interactive\n<Script src=\"/analytics.js\" strategy=\"afterInteractive\" />\n\n// lazyOnload - Load during idle time\n<Script src=\"/widget.js\" strategy=\"lazyOnload\" />\n\n// beforeInteractive - Load before page is interactive (use sparingly)\n// Only works in app/layout.tsx or pages/_document.js\n<Script src=\"/critical.js\" strategy=\"beforeInteractive\" />\n\n// worker - Load in web worker (experimental)\n<Script src=\"/heavy.js\" strategy=\"worker\" />\n```\n\n## Google Analytics\n\nUse `@next/third-parties` instead of inline GA scripts.\n\n```tsx\n// Bad: Inline GA script\n<Script src=\"https://www.googletagmanager.com/gtag/js?id=G-XXXXX\" />\n<Script id=\"ga-init\">\n  {`window.dataLayer = window.dataLayer || [];\n    function gtag(){dataLayer.push(arguments);}\n    gtag('js', new Date());\n    gtag('config', 'G-XXXXX');`}\n</Script>\n\n// Good: Next.js component\nimport { GoogleAnalytics } from '@next/third-parties/google'\n\nexport default function Layout({ children }) {\n  return (\n    <html>\n      <body>{children}</body>\n      <GoogleAnalytics gaId=\"G-XXXXX\" />\n    </html>\n  )\n}\n```\n\n## Google Tag Manager\n\n```tsx\nimport { GoogleTagManager } from '@next/third-parties/google'\n\nexport default function Layout({ children }) {\n  return (\n    <html>\n      <GoogleTagManager gtmId=\"GTM-XXXXX\" />\n      <body>{children}</body>\n    </html>\n  )\n}\n```\n\n## Other Third-Party Scripts\n\n```tsx\n// YouTube embed\nimport { YouTubeEmbed } from '@next/third-parties/google'\n;<YouTubeEmbed videoid=\"dQw4w9WgXcQ\" />\n\n// Google Maps\nimport { GoogleMapsEmbed } from '@next/third-parties/google'\n;<GoogleMapsEmbed apiKey=\"YOUR_API_KEY\" mode=\"place\" q=\"Brooklyn+Bridge,New+York,NY\" />\n```\n\n## Quick Reference\n\n| Pattern                                       | Issue                      | Fix                       |\n| --------------------------------------------- | -------------------------- | ------------------------- |\n| `<script src=\"...\">`                          | No optimization            | Use `next/script`         |\n| `<Script>` without id                         | Can't track inline scripts | Add `id` attribute        |\n| `<Script>` inside `<Head>`                    | Wrong placement            | Move outside Head         |\n| Inline GA/GTM scripts                         | No optimization            | Use `@next/third-parties` |\n| `strategy=\"beforeInteractive\"` outside layout | Won't work                 | Only use in root layout   |\n","isInternal":false,"tokens":882,"sizeBytes":3649},{"name":"self-hosting.md","path":".agents/skills/next-best-practices/self-hosting.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/next-best-practices/self-hosting.md","title":"Next-best-practices Skill","category":"anthropic-skill","format":"markdown","content":"# Self-Hosting Next.js\n\nDeploy Next.js outside of Vercel with confidence.\n\n## Quick Start: Standalone Output\n\nFor Docker or any containerized deployment, use standalone output:\n\n```js\n// next.config.js\nmodule.exports = {\n  output: 'standalone',\n}\n```\n\nThis creates a minimal `standalone` folder with only production dependencies:\n\n```\n.next/\n├── standalone/\n│   ├── server.js          # Entry point\n│   ├── node_modules/      # Only production deps\n│   └── .next/             # Build output\n└── static/                # Must be copied separately\n```\n\n## Docker Deployment\n\n### Dockerfile\n\n```dockerfile\nFROM node:20-alpine AS base\n\n# Install dependencies\nFROM base AS deps\nWORKDIR /app\nCOPY package.json package-lock.json* ./\nRUN npm ci\n\n# Build\nFROM base AS builder\nWORKDIR /app\nCOPY --from=deps /app/node_modules ./node_modules\nCOPY . .\nRUN npm run build\n\n# Production\nFROM base AS runner\nWORKDIR /app\n\nENV NODE_ENV=production\n\n# Create non-root user\nRUN addgroup --system --gid 1001 nodejs\nRUN adduser --system --uid 1001 nextjs\n\n# Copy standalone output\nCOPY --from=builder /app/.next/standalone ./\nCOPY --from=builder /app/.next/static ./.next/static\nCOPY --from=builder /app/public ./public\n\nUSER nextjs\n\nEXPOSE 3000\nENV PORT=3000\nENV HOSTNAME=\"0.0.0.0\"\n\nCMD [\"node\", \"server.js\"]\n```\n\n### Docker Compose\n\n```yaml\nversion: '3.8'\n\nservices:\n  web:\n    build: .\n    ports:\n      - '3000:3000'\n    environment:\n      - NODE_ENV=production\n    restart: unless-stopped\n    healthcheck:\n      test: ['CMD', 'wget', '-q', '--spider', 'http://localhost:3000/api/health']\n      interval: 30s\n      timeout: 10s\n      retries: 3\n```\n\n## PM2 Deployment\n\nFor traditional server deployments:\n\n```js\n// ecosystem.config.js\nmodule.exports = {\n  apps: [\n    {\n      name: 'nextjs',\n      script: '.next/standalone/server.js',\n      instances: 'max',\n      exec_mode: 'cluster',\n      env: {\n        NODE_ENV: 'production',\n        PORT: 3000,\n      },\n    },\n  ],\n}\n```\n\n```bash\nnpm run build\npm2 start ecosystem.config.js\n```\n\n## ISR and Cache Handlers\n\n### The Problem\n\nISR (Incremental Static Regeneration) uses filesystem caching by default. This **breaks with multiple instances**:\n\n- Instance A regenerates page → saves to its local disk\n- Instance B serves stale page → doesn't see Instance A's cache\n- Load balancer sends users to random instances → inconsistent content\n\n### Solution: Custom Cache Handler\n\nNext.js 14+ supports custom cache handlers for shared storage:\n\n```js\n// next.config.js\nmodule.exports = {\n  cacheHandler: require.resolve('./cache-handler.js'),\n  cacheMaxMemorySize: 0, // Disable in-memory cache\n}\n```\n\n#### Redis Cache Handler Example\n\n```js\n// cache-handler.js\nconst Redis = require('ioredis')\n\nconst redis = new Redis(process.env.REDIS_URL)\nconst CACHE_PREFIX = 'nextjs:'\n\nmodule.exports = class CacheHandler {\n  constructor(options) {\n    this.options = options\n  }\n\n  async get(key) {\n    const data = await redis.get(CACHE_PREFIX + key)\n    if (!data) return null\n\n    const parsed = JSON.parse(data)\n    return {\n      value: parsed.value,\n      lastModified: parsed.lastModified,\n    }\n  }\n\n  async set(key, data, ctx) {\n    const cacheData = {\n      value: data,\n      lastModified: Date.now(),\n    }\n\n    // Set TTL based on revalidate option\n    if (ctx?.revalidate) {\n      await redis.setex(CACHE_PREFIX + key, ctx.revalidate, JSON.stringify(cacheData))\n    } else {\n      await redis.set(CACHE_PREFIX + key, JSON.stringify(cacheData))\n    }\n  }\n\n  async revalidateTag(tags) {\n    // Implement tag-based invalidation\n    // This requires tracking which keys have which tags\n  }\n}\n```\n\n#### S3 Cache Handler Example\n\n```js\n// cache-handler.js\nconst { S3Client, GetObjectCommand, PutObjectCommand } = require('@aws-sdk/client-s3')\n\nconst s3 = new S3Client({ region: process.env.AWS_REGION })\nconst BUCKET = process.env.CACHE_BUCKET\n\nmodule.exports = class CacheHandler {\n  async get(key) {\n    try {\n      const response = await s3.send(\n        new GetObjectCommand({\n          Bucket: BUCKET,\n          Key: `cache/${key}`,\n        }),\n      )\n      const body = await response.Body.transformToString()\n      return JSON.parse(body)\n    } catch (err) {\n      if (err.name === 'NoSuchKey') return null\n      throw err\n    }\n  }\n\n  async set(key, data, ctx) {\n    await s3.send(\n      new PutObjectCommand({\n        Bucket: BUCKET,\n        Key: `cache/${key}`,\n        Body: JSON.stringify({\n          value: data,\n          lastModified: Date.now(),\n        }),\n        ContentType: 'application/json',\n      }),\n    )\n  }\n}\n```\n\n## What Works vs What Needs Setup\n\n| Feature              | Single Instance | Multi-Instance      | Notes                       |\n| -------------------- | --------------- | ------------------- | --------------------------- |\n| SSR                  | Yes             | Yes                 | No special setup            |\n| SSG                  | Yes             | Yes                 | Built at deploy time        |\n| ISR                  | Yes             | Needs cache handler | Filesystem cache breaks     |\n| Image Optimization   | Yes             | Yes                 | CPU-intensive, consider CDN |\n| Middleware           | Yes             | Yes                 | Runs on Node.js             |\n| Edge Runtime         | Limited         | Limited             | Some features Node-only     |\n| `revalidatePath/Tag` | Yes             | Needs cache handler | Must share cache            |\n| `next/font`          | Yes             | Yes                 | Fonts bundled at build      |\n| Draft Mode           | Yes             | Yes                 | Cookie-based                |\n\n## Image Optimization\n\nNext.js Image Optimization works out of the box but is CPU-intensive.\n\n### Option 1: Built-in (Simple)\n\nWorks automatically, but consider:\n\n- Set `deviceSizes` and `imageSizes` in config to limit variants\n- Use `minimumCacheTTL` to reduce regeneration\n\n```js\n// next.config.js\nmodule.exports = {\n  images: {\n    minimumCacheTTL: 60 * 60 * 24, // 24 hours\n    deviceSizes: [640, 750, 1080, 1920], // Limit sizes\n  },\n}\n```\n\n### Option 2: External Loader (Recommended for Scale)\n\nOffload to Cloudinary, Imgix, or similar:\n\n```js\n// next.config.js\nmodule.exports = {\n  images: {\n    loader: 'custom',\n    loaderFile: './lib/image-loader.js',\n  },\n}\n```\n\n```js\n// lib/image-loader.js\nexport default function cloudinaryLoader({ src, width, quality }) {\n  const params = ['f_auto', 'c_limit', `w_${width}`, `q_${quality || 'auto'}`]\n  return `https://res.cloudinary.com/demo/image/upload/${params.join(',')}${src}`\n}\n```\n\n## Environment Variables\n\n### Build-time vs Runtime\n\n```js\n// Available at build time only (baked into bundle)\nNEXT_PUBLIC_API_URL=https://api.example.com\n\n// Available at runtime (server-side only)\nDATABASE_URL=postgresql://...\nAPI_SECRET=...\n```\n\n### Runtime Configuration\n\nFor truly dynamic config, don't use `NEXT_PUBLIC_*`. Instead:\n\n```tsx\n// app/api/config/route.ts\nexport async function GET() {\n  return Response.json({\n    apiUrl: process.env.API_URL,\n    features: process.env.FEATURES?.split(','),\n  })\n}\n```\n\n## OpenNext: Serverless Without Vercel\n\n[OpenNext](https://open-next.js.org/) adapts Next.js for AWS Lambda, Cloudflare Workers, etc.\n\n```bash\nnpx create-sst@latest\n# or\nnpx @opennextjs/aws build\n```\n\nSupports:\n\n- AWS Lambda + CloudFront\n- Cloudflare Workers\n- Netlify Functions\n- Deno Deploy\n\n## Health Check Endpoint\n\nAlways include a health check for load balancers:\n\n```tsx\n// app/api/health/route.ts\nexport async function GET() {\n  try {\n    // Optional: check database connection\n    // await db.$queryRaw`SELECT 1`;\n\n    return Response.json({ status: 'healthy' }, { status: 200 })\n  } catch (error) {\n    return Response.json({ status: 'unhealthy' }, { status: 503 })\n  }\n}\n```\n\n## Pre-Deployment Checklist\n\n1. **Build locally first**: `npm run build` - catch errors before deploy\n2. **Test standalone output**: `node .next/standalone/server.js`\n3. **Set `output: 'standalone'`** for Docker\n4. **Configure cache handler** for multi-instance ISR\n5. **Set `HOSTNAME=\"0.0.0.0\"`** for containers\n6. **Copy `public/` and `.next/static/`** - not included in standalone\n7. **Add health check endpoint**\n8. **Test ISR revalidation** after deployment\n9. **Monitor memory usage** - Node.js defaults may need tuning\n\n## Testing Cache Handler\n\n**Critical**: Test your cache handler on every Next.js upgrade:\n\n```bash\n# Start multiple instances\nPORT=3001 node .next/standalone/server.js &\nPORT=3002 node .next/standalone/server.js &\n\n# Trigger ISR revalidation\ncurl http://localhost:3001/api/revalidate?path=/posts\n\n# Verify both instances see the update\ncurl http://localhost:3001/posts\ncurl http://localhost:3002/posts\n# Should return identical content\n```\n","isInternal":false,"tokens":2175,"sizeBytes":8803},{"name":"SKILL.md","path":".agents/skills/next-best-practices/SKILL.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/next-best-practices/SKILL.md","title":"next-best-practices","category":"anthropic-skill","format":"markdown","content":"---\nname: next-best-practices\ndescription: Next.js best practices - file conventions, RSC boundaries, data patterns, async APIs, metadata, error handling, route handlers, image/font optimization, bundling\nuser-invocable: false\n---\n\n# Next.js Best Practices\n\nApply these rules when writing or reviewing Next.js code.\n\n## File Conventions\n\nSee [file-conventions.md](./file-conventions.md) for:\n\n- Project structure and special files\n- Route segments (dynamic, catch-all, groups)\n- Parallel and intercepting routes\n- Middleware rename in v16 (middleware → proxy)\n\n## RSC Boundaries\n\nDetect invalid React Server Component patterns.\n\nSee [rsc-boundaries.md](./rsc-boundaries.md) for:\n\n- Async client component detection (invalid)\n- Non-serializable props detection\n- Server Action exceptions\n\n## Async Patterns\n\nNext.js 15+ async API changes.\n\nSee [async-patterns.md](./async-patterns.md) for:\n\n- Async `params` and `searchParams`\n- Async `cookies()` and `headers()`\n- Migration codemod\n\n## Runtime Selection\n\nSee [runtime-selection.md](./runtime-selection.md) for:\n\n- Default to Node.js runtime\n- When Edge runtime is appropriate\n\n## Directives\n\nSee [directives.md](./directives.md) for:\n\n- `'use client'`, `'use server'` (React)\n- `'use cache'` (Next.js)\n\n## Functions\n\nSee [functions.md](./functions.md) for:\n\n- Navigation hooks: `useRouter`, `usePathname`, `useSearchParams`, `useParams`\n- Server functions: `cookies`, `headers`, `draftMode`, `after`\n- Generate functions: `generateStaticParams`, `generateMetadata`\n\n## Error Handling\n\nSee [error-handling.md](./error-handling.md) for:\n\n- `error.tsx`, `global-error.tsx`, `not-found.tsx`\n- `redirect`, `permanentRedirect`, `notFound`\n- `forbidden`, `unauthorized` (auth errors)\n- `unstable_rethrow` for catch blocks\n\n## Data Patterns\n\nSee [data-patterns.md](./data-patterns.md) for:\n\n- Server Components vs Server Actions vs Route Handlers\n- Avoiding data waterfalls (`Promise.all`, Suspense, preload)\n- Client component data fetching\n\n## Route Handlers\n\nSee [route-handlers.md](./route-handlers.md) for:\n\n- `route.ts` basics\n- GET handler conflicts with `page.tsx`\n- Environment behavior (no React DOM)\n- When to use vs Server Actions\n\n## Metadata & OG Images\n\nSee [metadata.md](./metadata.md) for:\n\n- Static and dynamic metadata\n- `generateMetadata` function\n- OG image generation with `next/og`\n- File-based metadata conventions\n\n## Image Optimization\n\nSee [image.md](./image.md) for:\n\n- Always use `next/image` over `<img>`\n- Remote images configuration\n- Responsive `sizes` attribute\n- Blur placeholders\n- Priority loading for LCP\n\n## Font Optimization\n\nSee [font.md](./font.md) for:\n\n- `next/font` setup\n- Google Fonts, local fonts\n- Tailwind CSS integration\n- Preloading subsets\n\n## Bundling\n\nSee [bundling.md](./bundling.md) for:\n\n- Server-incompatible packages\n- CSS imports (not link tags)\n- Polyfills (already included)\n- ESM/CommonJS issues\n- Bundle analysis\n\n## Scripts\n\nSee [scripts.md](./scripts.md) for:\n\n- `next/script` vs native script tags\n- Inline scripts need `id`\n- Loading strategies\n- Google Analytics with `@next/third-parties`\n\n## Hydration Errors\n\nSee [hydration-error.md](./hydration-error.md) for:\n\n- Common causes (browser APIs, dates, invalid HTML)\n- Debugging with error overlay\n- Fixes for each cause\n\n## Suspense Boundaries\n\nSee [suspense-boundaries.md](./suspense-boundaries.md) for:\n\n- CSR bailout with `useSearchParams` and `usePathname`\n- Which hooks require Suspense boundaries\n\n## Parallel & Intercepting Routes\n\nSee [parallel-routes.md](./parallel-routes.md) for:\n\n- Modal patterns with `@slot` and `(.)` interceptors\n- `default.tsx` for fallbacks\n- Closing modals correctly with `router.back()`\n\n## Self-Hosting\n\nSee [self-hosting.md](./self-hosting.md) for:\n\n- `output: 'standalone'` for Docker\n- Cache handlers for multi-instance ISR\n- What works vs needs extra setup\n\n## Debug Tricks\n\nSee [debug-tricks.md](./debug-tricks.md) for:\n\n- MCP endpoint for AI-assisted debugging\n- Rebuild specific routes with `--debug-build-paths`\n","frontmatter":{"name":"next-best-practices","description":"Next.js best practices - file conventions, RSC boundaries, data patterns, async APIs, metadata, error handling, route handlers, image/font optimization, bundling","user-invocable":false},"isInternal":false,"tokens":1011,"sizeBytes":4022},{"name":"suspense-boundaries.md","path":".agents/skills/next-best-practices/suspense-boundaries.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/next-best-practices/suspense-boundaries.md","title":"Next-best-practices Skill","category":"anthropic-skill","format":"markdown","content":"# Suspense Boundaries\n\nClient hooks that cause CSR bailout without Suspense boundaries.\n\n## useSearchParams\n\nAlways requires Suspense boundary in static routes. Without it, the entire page becomes client-side rendered.\n\n```tsx\n// Bad: Entire page becomes CSR\n'use client'\n\nimport { useSearchParams } from 'next/navigation'\n\nexport default function SearchBar() {\n  const searchParams = useSearchParams()\n  return <div>Query: {searchParams.get('q')}</div>\n}\n```\n\n```tsx\n// Good: Wrap in Suspense\nimport { Suspense } from 'react'\nimport SearchBar from './search-bar'\n\nexport default function Page() {\n  return (\n    <Suspense fallback={<div>Loading...</div>}>\n      <SearchBar />\n    </Suspense>\n  )\n}\n```\n\n## usePathname\n\nRequires Suspense boundary when route has dynamic parameters.\n\n```tsx\n// In dynamic route [slug]\n// Bad: No Suspense\n'use client'\nimport { usePathname } from 'next/navigation'\n\nexport function Breadcrumb() {\n  const pathname = usePathname()\n  return <nav>{pathname}</nav>\n}\n```\n\n```tsx\n// Good: Wrap in Suspense\n<Suspense fallback={<BreadcrumbSkeleton />}>\n  <Breadcrumb />\n</Suspense>\n```\n\nIf you use `generateStaticParams`, Suspense is optional.\n\n## Quick Reference\n\n| Hook                | Suspense Required    |\n| ------------------- | -------------------- |\n| `useSearchParams()` | Yes                  |\n| `usePathname()`     | Yes (dynamic routes) |\n| `useParams()`       | No                   |\n| `useRouter()`       | No                   |\n","isInternal":false,"tokens":340,"sizeBytes":1471},{"name":"AGENTS.md","path":".agents/skills/vercel-react-best-practices/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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([fetchUser(), fetchConfig()])\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() {\n    return fetchUser()\n  },\n  async config() {\n    return fetchConfig()\n  },\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([userPromise, fetchConfig(), profilePromise])\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([configPromise, fetchData(session.user.id)])\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([fetchUser(), fetchPosts(), fetchComments()])\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({\n  enabled,\n  setEnabled,\n}: {\n  enabled: boolean\n  setEnabled: React.Dispatch<React.SetStateAction<boolean>>\n}) {\n  const [frames, setFrames] = useState<Frame[] | null>(null)\n\n  useEffect(() => {\n    if (enabled && !frames && typeof window !== 'undefined') {\n      import('./animation-frames.js').then((mod) => setFrames(mod.frames)).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(() => import('@vercel/analytics/react').then((m) => m.Analytics), { ssr: false })\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(() => import('./monaco-editor').then((m) => m.MonacoEditor), { ssr: false })\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 onMouseEnter={preload} onFocus={preload} onClick={onClick}>\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}>{children}</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    /* ... */\n  })\n  useKeyboardShortcut('k', () => {\n    /* ... */\n  })\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(\n      'prefs:v1',\n      JSON.stringify({\n        theme: user.preferences.theme,\n        notifications: user.preferences.notifications,\n      }),\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(\n    (newItems: Item[]) => {\n      setItems([...items, ...newItems])\n    },\n    [items],\n  ) // ❌ 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: MEDIUM (wasted computation on every render)**\n\nPass a functi\n\n<!-- [TRUNCATED AT 50KB] -->","isInternal":false,"tokens":11954,"sizeBytes":50070},{"name":"README.md","path":".agents/skills/vercel-react-best-practices/README.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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\n   ```bash\n   pnpm install\n   ```\n\n2. Build AGENTS.md from rules:\n\n   ```bash\n   pnpm build\n   ```\n\n3. Validate rule files:\n\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\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":852,"sizeBytes":3369},{"name":"advanced-event-handler-refs.md","path":".agents/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1/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":1071},{"name":"async-api-routes.md","path":".agents/skills/vercel-react-best-practices/rules/async-api-routes.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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([configPromise, fetchData(session.user.id)])\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":237,"sizeBytes":1113},{"name":"async-defer-await.md","path":".agents/skills/vercel-react-best-practices/rules/async-defer-await.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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":427,"sizeBytes":2008},{"name":"async-dependencies.md","path":".agents/skills/vercel-react-best-practices/rules/async-dependencies.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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([fetchUser(), fetchConfig()])\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() {\n    return fetchUser()\n  },\n  async config() {\n    return fetchConfig()\n  },\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([userPromise, fetchConfig(), profilePromise])\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":290,"sizeBytes":1294},{"name":"async-parallel.md","path":".agents/skills/vercel-react-best-practices/rules/async-parallel.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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([fetchUser(), fetchPosts(), fetchComments()])\n```\n","frontmatter":{"title":"Promise.all() for Independent Operations","impact":"CRITICAL","impactDescription":"2-10× improvement","tags":"async, parallelization, promises, waterfalls"},"isInternal":false,"tokens":140,"sizeBytes":646},{"name":"async-suspense-boundaries.md","path":".agents/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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":590,"sizeBytes":2506},{"name":"bundle-barrel-imports.md","path":".agents/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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":2372},{"name":"bundle-conditional.md","path":".agents/skills/vercel-react-best-practices/rules/bundle-conditional.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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({\n  enabled,\n  setEnabled,\n}: {\n  enabled: boolean\n  setEnabled: React.Dispatch<React.SetStateAction<boolean>>\n}) {\n  const [frames, setFrames] = useState<Frame[] | null>(null)\n\n  useEffect(() => {\n    if (enabled && !frames && typeof window !== 'undefined') {\n      import('./animation-frames.js').then((mod) => setFrames(mod.frames)).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":216,"sizeBytes":941},{"name":"bundle-defer-third-party.md","path":".agents/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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(() => import('@vercel/analytics/react').then((m) => m.Analytics), { ssr: false })\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":219,"sizeBytes":916},{"name":"bundle-dynamic-imports.md","path":".agents/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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(() => import('./monaco-editor').then((m) => m.MonacoEditor), { ssr: false })\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":195,"sizeBytes":787},{"name":"bundle-preload.md","path":".agents/skills/vercel-react-best-practices/rules/bundle-preload.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/vercel-react-best-practices/rules/bundle-preload.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fbundle-preload.md -->","frontmatter":{"title":"Preload Based on User Intent","impact":"MEDIUM","impactDescription":"reduces perceived latency","tags":"bundle, preload, user-intent, hover"},"isInternal":false,"tokens":259,"sizeBytes":1120},{"name":"client-event-listeners.md","path":".agents/skills/vercel-react-best-practices/rules/client-event-listeners.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/vercel-react-best-practices/rules/client-event-listeners.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fclient-event-listeners.md -->","frontmatter":{"title":"Deduplicate Global Event Listeners","impact":"LOW","impactDescription":"single listener for N components","tags":"client, swr, event-listeners, subscription"},"isInternal":false,"tokens":471,"sizeBytes":1982},{"name":"client-localstorage-schema.md","path":".agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fclient-localstorage-schema.md -->","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":442,"sizeBytes":1976},{"name":"client-passive-event-listeners.md","path":".agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fclient-passive-event-listeners.md -->","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":357,"sizeBytes":1636},{"name":"client-swr-dedup.md","path":".agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fclient-swr-dedup.md -->","frontmatter":{"title":"Use SWR for Automatic Deduplication","impact":"MEDIUM-HIGH","impactDescription":"automatic deduplication","tags":"client, swr, deduplication, data-fetching"},"isInternal":false,"tokens":310,"sizeBytes":1161},{"name":"js-batch-dom-css.md","path":".agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-batch-dom-css.md -->","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":744,"sizeBytes":3239},{"name":"js-cache-function-results.md","path":".agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-cache-function-results.md -->","frontmatter":{"title":"Cache Repeated Function Calls","impact":"MEDIUM","impactDescription":"avoid redundant computation","tags":"javascript, cache, memoization, performance"},"isInternal":false,"tokens":449,"sizeBytes":1935},{"name":"js-cache-property-access.md","path":".agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-cache-property-access.md -->","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/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/vercel-react-best-practices/rules/js-cache-storage.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-cache-storage.md -->","frontmatter":{"title":"Cache Storage API Calls","impact":"LOW-MEDIUM","impactDescription":"reduces expensive I/O","tags":"javascript, localStorage, storage, caching, performance"},"isInternal":false,"tokens":374,"sizeBytes":1640},{"name":"js-combine-iterations.md","path":".agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-combine-iterations.md -->","frontmatter":{"title":"Combine Multiple Array Iterations","impact":"LOW-MEDIUM","impactDescription":"reduces iterations","tags":"javascript, arrays, loops, performance"},"isInternal":false,"tokens":182,"sizeBytes":759},{"name":"js-early-exit.md","path":".agents/skills/vercel-react-best-practices/rules/js-early-exit.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/vercel-react-best-practices/rules/js-early-exit.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-early-exit.md -->","frontmatter":{"title":"Early Return from Functions","impact":"LOW-MEDIUM","impactDescription":"avoids unnecessary computation","tags":"javascript, functions, optimization, early-return"},"isInternal":false,"tokens":264,"sizeBytes":1129},{"name":"js-hoist-regexp.md","path":".agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-hoist-regexp.md -->","frontmatter":{"title":"Hoist RegExp Creation","impact":"LOW-MEDIUM","impactDescription":"avoids recreation","tags":"javascript, regexp, optimization, memoization"},"isInternal":false,"tokens":281,"sizeBytes":1026},{"name":"js-index-maps.md","path":".agents/skills/vercel-react-best-practices/rules/js-index-maps.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/vercel-react-best-practices/rules/js-index-maps.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-index-maps.md -->","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":237,"sizeBytes":847},{"name":"js-length-check-first.md","path":".agents/skills/vercel-react-best-practices/rules/js-length-check-first.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/vercel-react-best-practices/rules/js-length-check-first.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-length-check-first.md -->","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":1748},{"name":"js-min-max-loop.md","path":".agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fvercel-react-best-practices%2Frules%2Fjs-min-max-loop.md -->","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":597,"sizeBytes":2278},{"name":"js-set-map-lookups.md","path":".agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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":426,"sizeBytes":1758},{"name":"rendering-activity.md","path":".agents/skills/vercel-react-best-practices/rules/rendering-activity.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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":274,"sizeBytes":1119},{"name":"rendering-conditional-render.md","path":".agents/skills/vercel-react-best-practices/rules/rendering-conditional-render.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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":257,"sizeBytes":936},{"name":"rendering-content-visibility.md","path":".agents/skills/vercel-react-best-practices/rules/rendering-content-visibility.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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":205,"sizeBytes":817},{"name":"rendering-hoist-jsx.md","path":".agents/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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":226,"sizeBytes":989},{"name":"rendering-hydration-no-flicker.md","path":".agents/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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":467,"sizeBytes":2242},{"name":"rendering-hydration-suppress-warning.md","path":".agents/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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":189,"sizeBytes":850},{"name":"rendering-svg-precision.md","path":".agents/skills/vercel-react-best-practices/rules/rendering-svg-precision.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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":2070},{"name":"rerender-defer-reads.md","path":".agents/skills/vercel-react-best-practices/rules/rerender-defer-reads.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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":174,"sizeBytes":727},{"name":"rerender-functional-setstate.md","path":".agents/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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":660,"sizeBytes":2977},{"name":"rerender-lazy-state-init.md","path":".agents/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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":432,"sizeBytes":2000},{"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/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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":1171},{"name":"rerender-memo.md","path":".agents/skills/vercel-react-best-practices/rules/rerender-memo.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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":453,"sizeBytes":2002},{"name":"server-auth-actions.md","path":".agents/skills/vercel-react-best-practices/rules/server-auth-actions.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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":578,"sizeBytes":2638},{"name":"server-cache-lru.md","path":".agents/skills/vercel-react-best-practices/rules/server-cache-lru.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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":509,"sizeBytes":2225},{"name":"server-dedup-props.md","path":".agents/skills/vercel-react-best-practices/rules/server-dedup-props.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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":502,"sizeBytes":2064},{"name":"server-hoist-static-io.md","path":".agents/skills/vercel-react-best-practices/rules/server-hoist-static-io.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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":1020,"sizeBytes":4403},{"name":"server-parallel-fetching.md","path":".agents/skills/vercel-react-best-practices/rules/server-parallel-fetching.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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":244,"sizeBytes":1000},{"name":"SKILL.md","path":".agents/skills/vercel-react-best-practices/SKILL.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/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/JimmyLv/BibiGPT-v1?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":1545,"sizeBytes":6434},{"name":"SKILL.md","path":".agents/skills/web-design-guidelines/SKILL.md","rawUrl":"https://raw.githubusercontent.com/JimmyLv/BibiGPT-v1/HEAD/.agents/skills/web-design-guidelines/SKILL.md","title":"web-design-guidelines","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/JimmyLv/BibiGPT-v1?path=.agents%2Fskills%2Fweb-design-guidelines%2FSKILL.md -->","frontmatter":{"name":"web-design-guidelines","description":"Review UI code for Web Interface Guidelines compliance. Use when asked to \"review my UI\", \"check accessibility\", \"audit design\", \"review UX\", or \"check my site against best practices\".","metadata":{"author":"vercel","version":"1.0.0","argument-hint":"<file-or-pattern>"}},"isInternal":false,"tokens":268,"sizeBytes":1232}],"systemPromptSnippet":"<yakaai_skills repo=\"JimmyLv/BibiGPT-v1\">\n<!-- File: AGENTS.md (Tokens: ~653 | Category: root-instruction) -->\n# Repository Guidelines\n\n## Project Structure & Module Organization\n\nThis repository is a Next.js + TypeScript app. Keep feature changes close to existing folders:\n\n- `pages/`: route entry points (`pages/api/` for server endpoints).\n- `components/`: UI and reusable view logic (`components/ui/` for primitive controls).\n- `hooks/`: custom React hooks.\n- `lib/`: integration logic (OpenAI, Supabase, Upstash, LemonSqueezy).\n- `utils/`: pure helpers and schema utilities (`utils/schemas/`).\n- `styles/`: global and markdown styles.\n- `public/`: static assets and images.\n\n## Build, Test, and Development Commands\n\nUse `npm` with the committed lockfile:\n\n- `npm ci`: install exact dependencies.\n- `npm run dev`: start local dev server at `http://localhost:3000`.\n- `npm run build`: production build (also catches type/build issues).\n- `npm run start`: run the built app.\n- `docker compose up -d`: run containerized service after creating `.env`.\n\nThere is no dedicated lint/test script. Use:\n\n- `npx prettier --check .`: formatting check.\n- `npx tsc --noEmit`: explicit TypeScript check.\n\n## Coding Style & Naming Conventions\n\n- TypeScript strict mode is enabled; prefer explicit, narrow types.\n- Formatting is enforced by Prettier (`singleQuote: true`, `semi: false`, `trailingComma: all`, `printWidth: 120`).\n- Pre-commit hook runs `lint-staged` with `prettier --write --ignore-unknown`.\n- Use naming patterns already present:\n  - Components: `PascalCase.tsx` (example: `SummaryResult.tsx`)\n  - Hooks: `useSomething.ts` (example: `useSummarize.ts`)\n  - Helpers: `camelCase.ts` (example: `extractUrl.ts`)\n- Prefer path aliases `@/` or `~/` for root imports.\n\n## Testing Guidelines\n\nNo automated test framework is configured yet. For every behavioral change, include manual verification notes in your PR:\n\n- Main summary flow from `pages/index.tsx`\n- Any affected API endpoint in `pages/api/`\n- At least one source parsing case (YouTube or Bilibili)\n\nIf adding tests, colocate them as `*.test.ts` or `*.test.tsx` near changed modules.\n\n## Commit & Pull Request Guidelines\n\n- Follow Conventional Commits used by release tooling: `feat:`, `fix:`, `docs:`, `chore:` (optional scope, e.g. `chore(main):`).\n- Keep commit subjects short and imperative.\n- PRs should include: purpose, key changes, config/env impact, linked issue(s), and UI screenshots when relevant.\n- Before requesting review, run `npm run build` and ensure formatting is clean.\n\n## Security & Configuration Tips\n\n- Copy `.example.env` to `.env`; never commit secrets.\n- Required core services are OpenAI and Upstash; Sentry/Lemon/Segment are optional.\n- Avoid sharing built Docker images that contain `.env`.\n\n<!-- File: .agents/skills/agent-browser/references/authentication.md (Tokens: ~1188 | Category: anthropic-skill) -->\n# Authentication Patterns\n\nLogin flows, session persistence, OAuth, 2FA, and authenticated browsing.\n\n**Related**: [session-management.md](session-management.md) for state persistence details, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [Basic Login Flow](#basic-login-flow)\n- [Saving Authentication State](#saving-authentication-state)\n- [Restoring Authentication](#restoring-authentication)\n- [OAuth / SSO Flows](#oauth--sso-flows)\n- [Two-Factor Authentication](#two-factor-authentication)\n- [HTTP Basic Auth](#http-basic-auth)\n- [Cookie-Based Auth](#cookie-based-auth)\n- [Token Refresh Handling](#token-refresh-handling)\n- [Security Best Practices](#security-best-practices)\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\n   ```bash\n   echo \"*.auth-state.json\" >> .gitignore\n   ```\n\n2. **Use environment variables for credentials**\n\n   ```bash\n   agent-browser fill @e1 \"$APP_USERNAME\"\n   agent-browser fill @e2 \"$APP_PASSWORD\"\n   ```\n\n3. **Clean up after automation**\n\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/commands.md (Tokens: ~2205 | Category: anthropic-skill) -->\n# Command Reference\n\nComplete reference for all agent-browser commands. For quick start and common patterns, see SKILL.md.\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 click @e1 --new-tab # Click and open in new tab\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 and PDF\n\n```bash\nagent-browser screenshot          # Save to temporary directory\nagent-browser screenshot path.png # Save to 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\nagent-browser click @e1                   # Perform actions\nagent-browser record stop                 # Stop and save video\nagent-browser record restart ./take2.webm # Stop current + start new\n```\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 and 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 and 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\"          # Simple expressions only\nagent-browser eval -b \"<base64>\"             # Any JavaScript (base64 encoded)\nagent-browser eval --stdin                   # Read script from stdin\n```\n\nUse `-b`/`--base64` or `--stdin` for reliable execution. Shell escaping with nested quotes and special characters is error-prone.\n\n```bash\n# Base64 encode your script, then:\nagent-browser eval -b \"ZG9jdW1lbnQucXVlcnlTZWxlY3RvcignW3NyYyo9Il9uZXh0Il0nKQ==\"\n\n# Or use stdin with heredoc for multiline scripts:\ncat <<'EOF' | agent-browser eval --stdin\nconst links = document.querySelectorAll('a');\nArray.from(links).map(a => a.href);\nEOF\n```\n\n## State Management\n\n```bash\nagent-browser state save auth.json    # Save cookies, storage, auth state\nagent-browser state load auth.json    # Restore saved state\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 --proxy-bypass <hosts>  # Hosts to bypass proxy\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 --ignore-https-errors   # Ignore SSL certificate errors\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## 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 profiler start              # Start Chrome DevTools profiling\nagent-browser profiler stop trace.json    # Stop and save profile\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=\"browserbase\"         # Cloud browser provider\nAGENT_BROWSER_STREAM_PORT=\"9223\"             # WebSocket streaming port\nAGENT_BROWSER_HOME=\"/path/to/agent-browser\"  # Custom install location\n```\n\n<!-- File: .agents/skills/agent-browser/references/profiling.md (Tokens: ~788 | Category: anthropic-skill) -->\n# Profiling\n\nCapture Chrome DevTools performance profiles during browser automation for performance analysis.\n\n**Related**: [commands.md](commands.md) for full command reference, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [Basic Profiling](#basic-profiling)\n- [Profiler Commands](#profiler-commands)\n- [Categories](#categories)\n- [Use Cases](#use-cases)\n- [Output Format](#output-format)\n- [Viewing Profiles](#viewing-profiles)\n- [Limitations](#limitations)\n\n## Basic Profiling\n\n```bash\n# Start profiling\nagent-browser profiler start\n\n# Perform actions\nagent-browser navigate https://example.com\nagent-browser click \"#button\"\nagent-browser wait 1000\n\n# Stop and save\nagent-browser profiler stop ./trace.json\n```\n\n## Profiler Commands\n\n```bash\n# Start profiling with default categories\nagent-browser profiler start\n\n# Start with custom trace categories\nagent-browser profiler start --categories \"devtools.timeline,v8.execute,blink.user_timing\"\n\n# Stop profiling and save to file\nagent-browser profiler stop ./trace.json\n```\n\n## Categories\n\nThe `--categories` flag accepts a comma-separated list of Chrome trace categories. Default categories include:\n\n- `devtools.timeline` -- standard DevTools performance traces\n- `v8.execute` -- time spent running JavaScript\n- `blink` -- renderer events\n- `blink.user_timing` -- `performance.mark()` / `performance.measure()` calls\n- `latencyInfo` -- input-to-latency tracking\n- `renderer.scheduler` -- task scheduling and execution\n- `toplevel` -- broad-spectrum basic events\n\nSeveral `disabled-by-default-*` categories are also included for detailed timeline, call stack, and V8 CPU profiling data.\n\n## Use Cases\n\n### Diagnosing Slow Page Loads\n\n```bash\nagent-browser profiler start\nagent-browser navigate https://app.example.com\nagent-browser wait --load networkidle\nagent-browser profiler stop ./page-load-profile.json\n```\n\n### Profiling User Interactions\n\n```bash\nagent-browser navigate https://app.example.com\nagent-browser profiler start\nagent-browser click \"#submit\"\nagent-browser wait 2000\nagent-browser profiler stop ./interaction-profile.json\n```\n\n### CI Performance Regression Checks\n\n```bash\n#!/bin/bash\nagent-browser profiler start\nagent-browser navigate https://app.example.com\nagent-browser wait --load networkidle\nagent-browser profiler stop \"./profiles/build-${BUILD_ID}.json\"\n```\n\n## Output Format\n\nThe output is a JSON file in Chrome Trace Event format:\n\n```json\n{\n  \"traceEvents\": [\n    { \"cat\": \"devtools.timeline\", \"name\": \"RunTask\", \"ph\": \"X\", \"ts\": 12345, \"dur\": 100, ... },\n    ...\n  ],\n  \"metadata\": {\n    \"clock-domain\": \"LINUX_CLOCK_MONOTONIC\"\n  }\n}\n```\n\nThe `metadata.clock-domain` field is set based on the host platform (Linux or macOS). On Windows it is omitted.\n\n## Viewing Profiles\n\nLoad the output JSON file in any of these tools:\n\n- **Chrome DevTools**: Performance panel > Load profile (Ctrl+Shift+I > Performance)\n- **Perfetto UI**: https://ui.perfetto.dev/ -- drag and drop the JSON file\n- **Trace Viewer**: `chrome://tracing` in any Chromium browser\n\n## Limitations\n\n- Only works with Chromium-based browsers (Chrome, Edge). Not supported on Firefox or WebKit.\n- Trace data accumulates in memory while profiling is active (capped at 5 million events). Stop profiling promptly after the area of interest.\n- Data collection on stop has a 30-second timeout. If the browser is unresponsive, the stop command may fail.\n\n<!-- File: .agents/skills/agent-browser/references/proxy-support.md (Tokens: ~1248 | Category: anthropic-skill) -->\n# Proxy Support\n\nProxy configuration for geo-testing, rate limiting avoidance, and corporate environments.\n\n**Related**: [commands.md](commands.md) for global options, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [Basic Proxy Configuration](#basic-proxy-configuration)\n- [Authenticated Proxy](#authenticated-proxy)\n- [SOCKS Proxy](#socks-proxy)\n- [Proxy Bypass](#proxy-bypass)\n- [Common Use Cases](#common-use-cases)\n- [Verifying Proxy Connection](#verifying-proxy-connection)\n- [Troubleshooting](#troubleshooting)\n- [Best Practices](#best-practices)\n\n## Basic Proxy Configuration\n\nUse the `--proxy` flag or set proxy via environment variable:\n\n```bash\n# Via CLI flag\nagent-browser --proxy \"http://proxy.example.com:8080\" open https://example.com\n\n# Via environment variable\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 using `--proxy-bypass` or `NO_PROXY`:\n\n```bash\n# Via CLI flag\nagent-browser --proxy \"http://proxy.example.com:8080\" --proxy-bypass \"localhost,*.internal.com\" open https://example.com\n\n# Via environment variable\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: ~1017 | Category: anthropic-skill) -->\n# Session Management\n\nMultiple isolated browser sessions with state persistence and concurrent browsing.\n\n**Related**: [authentication.md](authentication.md) for login patterns, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [Named Sessions](#named-sessions)\n- [Session Isolation Properties](#session-isolation-properties)\n- [Session State Persistence](#session-state-persistence)\n- [Common Patterns](#common-patterns)\n- [Default Session](#default-session)\n- [Session Cleanup](#session-cleanup)\n- [Best Practices](#best-practices)\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\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: ~1129 | Category: anthropic-skill) -->\n# Snapshot and Refs\n\nCompact element references that reduce context usage dramatically for AI agents.\n\n**Related**: [commands.md](commands.md) for full command reference, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [How Refs Work](#how-refs-work)\n- [Snapshot Command](#the-snapshot-command)\n- [Using Refs](#using-refs)\n- [Ref Lifecycle](#ref-lifecycle)\n- [Best Practices](#best-practices)\n- [Ref Notation Details](#ref-notation-details)\n- [Troubleshooting](#troubleshooting)\n\n## How Refs Work\n\nTraditional approach:\n\n```\nFull DOM/HTML → AI parses → CSS selector → Action (~3000-5000 tokens)\n```\n\nagent-browser approach:\n\n```\nCompact snapshot → @refs assigned → Direct interaction (~200-400 tokens)\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 down to reveal element\nagent-browser scroll down 1000\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: ~880 | Category: anthropic-skill) -->\n# Video Recording\n\nCapture browser automation as video for debugging, documentation, or verification.\n\n**Related**: [commands.md](commands.md) for full command reference, [SKILL.md](../SKILL.md) for quick start.\n\n## Contents\n\n- [Basic Recording](#basic-recording)\n- [Recording Commands](#recording-commands)\n- [Use Cases](#use-cases)\n- [Best Practices](#best-practices)\n- [Output Format](#output-format)\n- [Limitations](#limitations)\n\n## Basic Recording\n\n```bash\n# Start recording\nagent-browser record start ./demo.webm\n\n# Perform actions\nagent-browser open https://example.com\nagent-browser snapshot -i\nagent-browser click @e1\nagent-browser fill @e2 \"test input\"\n\n# Stop and save\nagent-browser record stop\n```\n\n## Recording Commands\n\n```bash\n# Start recording to file\nagent-browser record start ./output.webm\n\n# Stop current recording\nagent-browser record stop\n\n# Restart with new file (stops current + starts new)\nagent-browser record restart ./take2.webm\n```\n\n## Use Cases\n\n### Debugging Failed Automation\n\n```bash\n#!/bin/bash\n# Record automation for debugging\n\nagent-browser record start ./debug-$(date +%Y%m%d-%H%M%S).webm\n\n# Run your automation\nagent-browser open https://app.example.com\nagent-browser snapshot -i\nagent-browser click @e1 || {\n    echo \"Click failed - check recording\"\n    agent-browser record stop\n    exit 1\n}\n\nagent-browser record stop\n```\n\n### Documentation Generation\n\n```bash\n#!/bin/bash\n# Record workflow for documentation\n\nagent-browser record start ./docs/how-to-login.webm\n\nagent-browser open https://app.example.com/login\nagent-browser wait 1000  # Pause for visibility\n\nagent-browser snapshot -i\nagent-browser fill @e1 \"demo@example.com\"\nagent-browser wait 500\n\nagent-browser fill @e2 \"password\"\nagent-browser wait 500\n\nagent-browser click @e3\nagent-browser wait --load networkidle\nagent-browser wait 1000  # Show result\n\nagent-browser record stop\n```\n\n### CI/CD Test Evidence\n\n```bash\n#!/bin/bash\n# Record E2E test runs for CI artifacts\n\nTEST_NAME=\"${1:-e2e-test}\"\nRECORDING_DIR=\"./test-recordings\"\nmkdir -p \"$RECORDING_DIR\"\n\nagent-browser record start \"$RECORDING_DIR/$TEST_NAME-$(date +%s).webm\"\n\n# Run test\nif run_e2e_test; then\n    echo \"Test passed\"\nelse\n    echo \"Test failed - recording saved\"\nfi\n\nagent-browser record stop\n```\n\n## Best Practices\n\n### 1. Add Pauses for Clarity\n\n```bash\n# Slow down for human viewing\nagent-browser click @e1\nagent-browser wait 500  # Let viewer see result\n```\n\n### 2. Use Descriptive Filenames\n\n```bash\n# Include context in filename\nagent-browser record start ./recordings/login-flow-2024-01-15.webm\nagent-browser record start ./recordings/checkout-test-run-42.webm\n```\n\n### 3. Handle Recording in Error Cases\n\n```bash\n#!/bin/bash\nset -e\n\ncleanup() {\n    agent-browser record stop 2>/dev/null || true\n    agent-browser close 2>/dev/null || true\n}\ntrap cleanup EXIT\n\nagent-browser record start ./automation.webm\n# ... automation steps ...\n```\n\n### 4. Combine with Screenshots\n\n```bash\n# Record video AND capture key frames\nagent-browser record start ./flow.webm\n\nagent-browser open https://example.com\nagent-browser screenshot ./screenshots/step1-homepage.png\n\nagent-browser click @e1\nagent-browser screenshot ./screenshots/step2-after-click.png\n\nagent-browser record stop\n```\n\n## Output Format\n\n- Default format: WebM (VP8/VP9 codec)\n- Compatible with all modern browsers and video players\n- Compressed but high quality\n\n## Limitations\n\n- Recording adds slight overhead to automation\n- Large recordings can consume significant disk space\n- Some headless environments may have codec limitations\n\n<!-- File: .agents/skills/agent-browser/SKILL.md (Tokens: ~4398 | Category: anthropic-skill) -->\n---\nname: agent-browser\ndescription: Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to \"open a website\", \"fill out a form\", \"click a button\", \"take a screenshot\", \"scrape data from a page\", \"test this web app\", \"login to a site\", \"automate browser actions\", or any task requiring programmatic web interaction.\nallowed-tools: Bash(npx agent-browser:*), Bash(agent-browser:*)\n---\n\n# Browser Automation with agent-browser\n\n## Core Workflow\n\nEvery browser automation follows this pattern:\n\n1. **Navigate**: `agent-browser open <url>`\n2. **Snapshot**: `agent-browser snapshot -i` (get element refs like `@e1`, `@e2`)\n3. **Interact**: Use refs to click, fill, select\n4. **Re-snapshot**: After navigation or DOM changes, get fresh refs\n\n```bash\nagent-browser open https://example.com/form\nagent-browser snapshot -i\n# Output: @e1 [input type=\"email\"], @e2 [input type=\"password\"], @e3 [button] \"Submit\"\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## Command Chaining\n\nCommands can be chained with `&&` in a single shell invocation. The browser persists between commands via a background daemon, so chaining is safe and more efficient than separate calls.\n\n```bash\n# Chain open + wait + snapshot in one call\nagent-browser open https://example.com && agent-browser wait --load networkidle && agent-browser snapshot -i\n\n# Chain multiple interactions\nagent-browser fill @e1 \"user@example.com\" && agent-browser fill @e2 \"password123\" && agent-browser click @e3\n\n# Navigate and capture\nagent-browser open https://example.com && agent-browser wait --load networkidle && agent-browser screenshot page.png\n```\n\n**When to chain:** Use `&&` when you don't need to read the output of an intermediate command before proceeding (e.g., open + wait + screenshot). Run commands separately when you need to parse the output first (e.g., snapshot to discover refs, then interact using those refs).\n\n## Essential Commands\n\n```bash\n# Navigation\nagent-browser open <url>              # Navigate (aliases: goto, navigate)\nagent-browser close                   # Close browser\n\n# Snapshot\nagent-browser snapshot -i             # Interactive elements with refs (recommended)\nagent-browser snapshot -i -C          # Include cursor-interactive elements (divs with onclick, cursor:pointer)\nagent-browser snapshot -s \"#selector\" # Scope to CSS selector\n\n# Interaction (use @refs from snapshot)\nagent-browser click @e1               # Click element\nagent-browser click @e1 --new-tab     # Click and open in new tab\nagent-browser fill @e2 \"text\"         # Clear and type text\nagent-browser type @e2 \"text\"         # Type without clearing\nagent-browser select @e1 \"option\"     # Select dropdown option\nagent-browser check @e1               # Check checkbox\nagent-browser press Enter             # Press key\nagent-browser keyboard type \"text\"    # Type at current focus (no selector)\nagent-browser keyboard inserttext \"text\"  # Insert without key events\nagent-browser scroll down 500         # Scroll page\nagent-browser scroll down 500 --selector \"div.content\"  # Scroll within a specific container\n\n# Get information\nagent-browser get text @e1            # Get element text\nagent-browser get url                 # Get current URL\nagent-browser get title               # Get page title\n\n# Wait\nagent-browser wait @e1                # Wait for element\nagent-browser wait --load networkidle # Wait for network idle\nagent-browser wait --url \"**/page\"    # Wait for URL pattern\nagent-browser wait 2000               # Wait milliseconds\n\n# Downloads\nagent-browser download @e1 ./file.pdf          # Click element to trigger download\nagent-browser wait --download ./output.zip     # Wait for any download to complete\nagent-browser --download-path ./downloads open <url>  # Set default download directory\n\n# Capture\nagent-browser screenshot              # Screenshot to temp dir\nagent-browser screenshot --full       # Full page screenshot\nagent-browser screenshot --annotate   # Annotated screenshot with numbered element labels\nagent-browser pdf output.pdf          # Save as PDF\n\n# Diff (compare page states)\nagent-browser diff snapshot                          # Compare current vs last snapshot\nagent-browser diff snapshot --baseline before.txt    # Compare current vs saved file\nagent-browser diff screenshot --baseline before.png  # Visual pixel diff\nagent-browser diff url <url1> <url2>                 # Compare two pages\nagent-browser diff url <url1> <url2> --wait-until networkidle  # Custom wait strategy\nagent-browser diff url <url1> <url2> --selector \"#main\"  # Scope to element\n```\n\n## Common Patterns\n\n### Form Submission\n\n```bash\nagent-browser open https://example.com/signup\nagent-browser snapshot -i\nagent-browser fill @e1 \"Jane Doe\"\nagent-browser fill @e2 \"jane@example.com\"\nagent-browser select @e3 \"California\"\nagent-browser check @e4\nagent-browser click @e5\nagent-browser wait --load networkidle\n```\n\n### Authentication with Auth Vault (Recommended)\n\n```bash\n# Save credentials once (encrypted with AGENT_BROWSER_ENCRYPTION_KEY)\n# Recommended: pipe password via stdin to avoid shell history exposure\necho \"pass\" | agent-browser auth save github --url https://github.com/login --username user --password-stdin\n\n# Login using saved profile (LLM never sees password)\nagent-browser auth login github\n\n# List/show/delete profiles\nagent-browser auth list\nagent-browser auth show github\nagent-browser auth delete github\n```\n\n### Authentication with State Persistence\n\n```bash\n# Login once and save state\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# Reuse in future sessions\nagent-browser state load auth.json\nagent-browser open https://app.example.com/dashboard\n```\n\n### Session Persistence\n\n```bash\n# Auto-save/restore cookies and localStorage across browser restarts\nagent-browser --session-name myapp open https://app.example.com/login\n# ... login flow ...\nagent-browser close  # State auto-saved to ~/.agent-browser/sessions/\n\n# Next time, state is auto-loaded\nagent-browser --session-name myapp open https://app.example.com/dashboard\n\n# Encrypt state at rest\nexport AGENT_BROWSER_ENCRYPTION_KEY=$(openssl rand -hex 32)\nagent-browser --session-name secure open https://app.example.com\n\n# Manage saved states\nagent-browser state list\nagent-browser state show myapp-default.json\nagent-browser state clear myapp\nagent-browser state clean --older-than 7\n```\n\n### Data Extraction\n\n```bash\nagent-browser open https://example.com/products\nagent-browser snapshot -i\nagent-browser get text @e5           # Get specific element text\nagent-browser get text body > page.txt  # Get all page text\n\n# JSON output for parsing\nagent-browser snapshot -i --json\nagent-browser get text @e1 --json\n```\n\n### Parallel Sessions\n\n```bash\nagent-browser --session site1 open https://site-a.com\nagent-browser --session site2 open https://site-b.com\n\nagent-browser --session site1 snapshot -i\nagent-browser --session site2 snapshot -i\n\nagent-browser session list\n```\n\n### Connect to Existing Chrome\n\n```bash\n# Auto-discover running Chrome with remote debugging enabled\nagent-browser --auto-connect open https://example.com\nagent-browser --auto-connect snapshot\n\n# Or with explicit CDP port\nagent-browser --cdp 9222 snapshot\n```\n\n### Color Scheme (Dark Mode)\n\n```bash\n# Persistent dark mode via flag (applies to all pages and new tabs)\nagent-browser --color-scheme dark open https://example.com\n\n# Or via environment variable\nAGENT_BROWSER_COLOR_SCHEME=dark agent-browser open https://example.com\n\n# Or set during session (persists for subsequent commands)\nagent-browser set media dark\n```\n\n### Visual Browser (Debugging)\n\n```bash\nagent-browser --headed open https://example.com\nagent-browser highlight @e1          # Highlight element\nagent-browser record start demo.webm # Record session\nagent-browser profiler start         # Start Chrome DevTools profiling\nagent-browser profiler stop trace.json # Stop and save profile (path optional)\n```\n\nUse `AGENT_BROWSER_HEADED=1` to enable headed mode via environment variable. Browser extensions work in both headed and headless mode.\n\n### Local Files (PDFs, HTML)\n\n```bash\n# Open local files with file:// URLs\nagent-browser --allow-file-access open file:///path/to/document.pdf\nagent-browser --allow-file-access open file:///path/to/page.html\nagent-browser screenshot output.png\n```\n\n### iOS Simulator (Mobile Safari)\n\n```bash\n# List available iOS simulators\nagent-browser device list\n\n# Launch Safari on a specific device\nagent-browser -p ios --device \"iPhone 16 Pro\" open https://example.com\n\n# Same workflow as desktop - snapshot, interact, re-snapshot\nagent-browser -p ios snapshot -i\nagent-browser -p ios tap @e1          # Tap (alias for click)\nagent-browser -p ios fill @e2 \"text\"\nagent-browser -p ios swipe up         # Mobile-specific gesture\n\n# Take screenshot\nagent-browser -p ios screenshot mobile.png\n\n# Close session (shuts down simulator)\nagent-browser -p ios close\n```\n\n**Requirements:** macOS with Xcode, Appium (`npm install -g appium && appium driver install xcuitest`)\n\n**Real devices:** Works with physical iOS devices if pre-configured. Use `--device \"<UDID>\"` where UDID is from `xcrun xctrace list devices`.\n\n## Security\n\nAll security features are opt-in. By default, agent-browser imposes no restrictions on navigation, actions, or output.\n\n### Content Boundaries (Recommended for AI Agents)\n\nEnable `--content-boundaries` to wrap page-sourced output in markers that help LLMs distinguish tool output from untrusted page content:\n\n```bash\nexport AGENT_BROWSER_CONTENT_BOUNDARIES=1\nagent-browser snapshot\n# Output:\n# --- AGENT_BROWSER_PAGE_CONTENT nonce=<hex> origin=https://example.com ---\n# [accessibility tree]\n# --- END_AGENT_BROWSER_PAGE_CONTENT nonce=<hex> ---\n```\n\n### Domain Allowlist\n\nRestrict navigation to trusted domains. Wildcards like `*.example.com` also match the bare domain `example.com`. Sub-resource requests, WebSocket, and EventSource connections to non-allowed domains are also blocked. Include CDN domains your target pages depend on:\n\n```bash\nexport AGENT_BROWSER_ALLOWED_DOMAINS=\"example.com,*.example.com\"\nagent-browser open https://example.com        # OK\nagent-browser open https://malicious.com       # Blocked\n```\n\n### Action Policy\n\nUse a policy file to gate destructive actions:\n\n```bash\nexport AGENT_BROWSER_ACTION_POLICY=./policy.json\n```\n\nExample `policy.json`:\n\n```json\n{ \"default\": \"deny\", \"allow\": [\"navigate\", \"snapshot\", \"click\", \"scroll\", \"wait\", \"get\"] }\n```\n\nAuth vault operations (`auth login`, etc.) bypass action policy but domain allowlist still applies.\n\n### Output Limits\n\nPrevent context flooding from large pages:\n\n```bash\nexport AGENT_BROWSER_MAX_OUTPUT=50000\n```\n\n## Diffing (Verifying Changes)\n\nUse `diff snapshot` after performing an action to verify it had the intended effect. This compares the current accessibility tree against the last snapshot taken in the session.\n\n```bash\n# Typical workflow: snapshot -> action -> diff\nagent-browser snapshot -i          # Take baseline snapshot\nagent-browser click @e2            # Perform action\nagent-browser diff snapshot        # See what changed (auto-compares to last snapshot)\n```\n\nFor visual regression testing or monitoring:\n\n```bash\n# Save a baseline screenshot, then compare later\nagent-browser screenshot baseline.png\n# ... time passes or changes are made ...\nagent-browser diff screenshot --baseline baseline.png\n\n# Compare staging vs production\nagent-browser diff url https://staging.example.com https://prod.example.com --screenshot\n```\n\n`diff snapshot` output uses `+` for additions and `-` for removals, similar to git diff. `diff screenshot` produces a diff image with changed pixels highlighted in red, plus a mismatch percentage.\n\n## Timeouts and Slow Pages\n\nThe default Playwright timeout is 25 seconds for local browsers. This can be overridden with the `AGENT_BROWSER_DEFAULT_TIMEOUT` environment variable (value in milliseconds). For slow websites or large pages, use explicit waits instead of relying on the default timeout:\n\n```bash\n# Wait for network activity to settle (best for slow pages)\nagent-browser wait --load networkidle\n\n# Wait for a specific element to appear\nagent-browser wait \"#content\"\nagent-browser wait @e1\n\n# Wait for a specific URL pattern (useful after redirects)\nagent-browser wait --url \"**/dashboard\"\n\n# Wait for a JavaScript condition\nagent-browser wait --fn \"document.readyState === 'complete'\"\n\n# Wait a fixed duration (milliseconds) as a last resort\nagent-browser wait 5000\n```\n\nWhen dealing with consistently slow websites, use `wait --load networkidle` after `open` to ensure the page is fully loaded before taking a snapshot. If a specific element is slow to render, wait for it directly with `wait <selector>` or `wait @ref`.\n\n## Session Management and Cleanup\n\nWhen running multiple agents or automations concurrently, always use named sessions to avoid conflicts:\n\n```bash\n# Each agent gets its own isolated session\nagent-browser --session agent1 open site-a.com\nagent-browser --session agent2 open site-b.com\n\n# Check active sessions\nagent-browser session list\n```\n\nAlways close your browser session when done to avoid leaked processes:\n\n```bash\nagent-browser close                    # Close default session\nagent-browser --session agent1 close   # Close specific session\n```\n\nIf a previous session was not closed properly, the daemon may still be running. Use `agent-browser close` to clean it up before starting new work.\n\n## Ref Lifecycle (Important)\n\nRefs (`@e1`, `@e2`, etc.) are invalidated when the page changes. Always re-snapshot after:\n\n- Clicking links or buttons that navigate\n- Form submissions\n- Dynamic content loading (dropdowns, modals)\n\n```bash\nagent-browser click @e5              # Navigates to new page\nagent-browser snapshot -i            # MUST re-snapshot\nagent-browser click @e1              # Use new refs\n```\n\n## Annotated Screenshots (Vision Mode)\n\nUse `--annotate` to take a screenshot with numbered labels overlaid on interactive elements. Each label `[N]` maps to ref `@eN`. This also caches refs, so you can interact with elements immediately without a separate snapshot.\n\n```bash\nagent-browser screenshot --annotate\n# Output includes the image path and a legend:\n#   [1] @e1 button \"Submit\"\n#   [2] @e2 link \"Home\"\n#   [3] @e3 textbox \"Email\"\nagent-browser click @e2              # Click using ref from annotated screenshot\n```\n\nUse annotated screenshots when:\n\n- The page has unlabeled icon buttons or visual-only elements\n- You need to verify visual layout or styling\n- Canvas or chart elements are present (invisible to text snapshots)\n- You need spatial reasoning about element positions\n\n## Semantic Locators (Alternative to Refs)\n\nWhen refs are unavailable or unreliable, use semantic locators:\n\n```bash\nagent-browser find text \"Sign In\" click\nagent-browser find label \"Email\" fill \"user@test.com\"\nagent-browser find role button click --name \"Submit\"\nagent-browser find placeholder \"Search\" type \"query\"\nagent-browser find testid \"submit-btn\" click\n```\n\n## JavaScript Evaluation (eval)\n\nUse `eval` to run JavaScript in the browser context. **Shell quoting can corrupt complex expressions** -- use `--stdin` or `-b` to avoid issues.\n\n```bash\n# Simple expressions work with regular quoting\nagent-browser eval 'document.title'\nagent-browser eval 'document.querySelectorAll(\"img\").length'\n\n# Complex JS: use --stdin with heredoc (RECOMMENDED)\nagent-browser eval --stdin <<'EVALEOF'\nJSON.stringify(\n  Array.from(document.querySelectorAll(\"img\"))\n    .filter(i => !i.alt)\n    .map(i => ({ src: i.src.split(\"/\").pop(), width: i.width }))\n)\nEVALEOF\n\n# Alternative: base64 encoding (avoids all shell escaping issues)\nagent-browser eval -b \"$(echo -n 'Array.from(document.querySelectorAll(\"a\")).map(a => a.href)' | base64)\"\n```\n\n**Why this matters:** When the shell processes your command, inner double quotes, `!` characters (history expansion), backticks, and `$()` can all corrupt the JavaScript before it reaches agent-browser. The `--stdin` and `-b` flags bypass shell interpretation entirely.\n\n**Rules of thumb:**\n\n- Single-line, no nested quotes -> regular `eval 'expression'` with single quotes is fine\n- Nested quotes, arrow functions, template literals, or multiline -> use `eval --stdin <<'EVALEOF'`\n- Programmatic/generated scripts -> use `eval -b` with base64\n\n## Configuration File\n\nCreate `agent-browser.json` in the project root for persistent settings:\n\n```json\n{\n  \"headed\": true,\n  \"proxy\": \"http://localhost:8080\",\n  \"profile\": \"./browser-data\"\n}\n```\n\nPriority (lowest to highest): `~/.agent-browser/config.json` < `./agent-browser.json` < env vars < CLI flags. Use `--config <path>` or `AGENT_BROWSER_CONFIG` env var for a custom config file (exits with error if missing/invalid). All CLI options map to camelCase keys (e.g., `--executable-path` -> `\"executablePath\"`). Boolean flags accept `true`/`false` values (e.g., `--headed false` overrides config). Extensions from user and project configs are merged, not replaced.\n\n## Deep-Dive Documentation\n\n| Reference                                                            | When to Use                                               |\n| -------------------------------------------------------------------- | --------------------------------------------------------- |\n| [references/commands.md](references/commands.md)                     | Full command reference with all options                   |\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/profiling.md](references/profiling.md)                   | Chrome DevTools profiling for performance analysis        |\n| [references/proxy-support.md](references/proxy-support.md)           | Proxy configuration, geo-testing, rotating proxies        |\n\n## Experimental: Native Mode\n\nagent-browser has an experimental native Rust daemon that communicates with Chrome directly via CDP, bypassing Node.js and Playwright entirely. It is opt-in and not recommended for production use yet.\n\n```bash\n# Enable via flag\nagent-browser --native open example.com\n\n# Enable via environment variable (avoids passing --native every time)\nexport AGENT_BROWSER_NATIVE=1\nagent-browser open example.com\n```\n\nThe native daemon supports Chromium and Safari (via WebDriver). Firefox and WebKit are not yet supported. All core commands (navigate, snapshot, click, fill, screenshot, cookies, storage, tabs, eval, etc.) work identically in native mode. Use `agent-browser close` before switching between native and default mode within the same session.\n\n## Ready-to-Use Templates\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\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<!-- 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: ~2778 | 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 <pre key={`${message.id}-${i}`}>{JSON.stringify(part.toolInvocation, null, 2)}</pre>\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 (part.type === 'tool-getWeather' && (part.state === 'input-available' || part.state === 'output-available')) {\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 (part.type === 'tool-getWeather' && (part.state === 'input-available' || part.state === 'output-available')) {\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: ~1240 | 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({ invocation }: { invocation: WeatherToolInvocation }) {\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/bibi/SKILL.md (Tokens: ~827 | Category: anthropic-skill) -->\n---\nname: bibi\ndescription: >\n  BibiGPT CLI for summarizing videos, audio, and podcasts directly in the terminal.\n  Use when the user wants to summarize a URL (YouTube, Bilibili, podcast, etc.)\n  or check their BibiGPT authentication status.\n  Requires the BibiGPT desktop app installed with an active login session,\n  or a BIBI_API_TOKEN environment variable set.\n---\n\n# BibiGPT CLI (`bibi`)\n\nSummarize videos, audio, and podcasts from the terminal using the BibiGPT API.\n\n## Installation\n\n### macOS (Homebrew)\n\n```bash\nbrew install --cask jimmylv/bibigpt/bibigpt\n```\n\n### Windows\n\nDownload the installer from: **https://bibigpt.co/download/desktop**\n\n> `winget install BibiGPT` is pending review and not yet available.\n\n### Verify installation\n\n```bash\nbibi --version\n```\n\n## Authentication\n\nAfter installing, the user must log in via the desktop app at least once. The CLI reads the saved session automatically from the desktop app's settings.\n\nAlternatively, set an API token:\n\n```bash\nexport BIBI_API_TOKEN=<token>\n```\n\nOn Windows (PowerShell):\n\n```powershell\n$env:BIBI_API_TOKEN=\"<token>\"\n```\n\n## Commands\n\n### Summarize a URL\n\n**Important**: URLs containing `?` or `&` must be quoted to avoid shell glob errors.\n\n```bash\n# Basic summary (Markdown output to stdout)\nbibi summarize \"<URL>\"\n\n# Async mode — recommended for long videos (>30min)\nbibi summarize \"<URL>\" --async\n\n# Chapter-by-chapter summary\nbibi summarize \"<URL>\" --chapter\n\n# Fetch subtitles/transcript only (no AI summary)\nbibi summarize \"<URL>\" --subtitle\n\n# Full JSON response\nbibi summarize \"<URL>\" --json\n\n# Combine flags\nbibi summarize \"<URL>\" --subtitle --json\n```\n\n**Supported URL types**: YouTube, Bilibili, podcasts, audio files, and any URL supported by BibiGPT.\n\n### Authentication\n\n```bash\n# Check current auth status\nbibi auth check\n\n# Open browser to log in\nbibi auth login\n\n# Show how to set API token\nbibi auth set-token <TOKEN>\n```\n\n### Updates\n\n```bash\n# Check if a new version is available\nbibi check-update\n\n# Download and install the latest version\nbibi self-update\n```\n\n## Output Format\n\n- **Default**: Markdown summary text sent to stdout. Progress info goes to stderr.\n- **--json**: Complete API response as pretty-printed JSON to stdout.\n\nThis means you can pipe the output:\n\n```bash\nbibi summarize \"<URL>\" > summary.md\nbibi summarize \"<URL>\" --json | jq '.summary'\n```\n\n## Error Handling\n\n| Exit Code | Meaning                                        |\n| --------- | ---------------------------------------------- |\n| 0         | Success                                        |\n| 1         | Error (auth missing, API error, timeout, etc.) |\n\n| HTTP Status | User Action                                                      |\n| ----------- | ---------------------------------------------------------------- |\n| 401         | Token expired — run `bibi auth login` or re-login in desktop app |\n| 402/403     | Quota exceeded — visit https://bibigpt.co/pricing                |\n| 429         | Rate limited — wait and retry                                    |\n\n## Usage Tips\n\n- For very long videos, use `--async` to avoid HTTP timeout.\n- Use `--subtitle` to get raw subtitles/transcript without AI summarization.\n- Use `--json` when you need structured data (e.g., `sourceUrl`, `htmlUrl`, `detail`).\n- The `--chapter` flag provides section-by-section summaries, useful for lectures or tutorials.\n- The CLI does NOT open any GUI window — all output goes to the terminal.\n- Run `bibi check-update` periodically to get new features and bug fixes.\n\n<!-- File: .agents/skills/find-skills/SKILL.md (Tokens: ~1110 | Category: anthropic-skill) -->\n---\nname: find-skills\ndescription: Helps users discover and install agent skills when they ask questions like \"how do I do X\", \"find a skill for X\", \"is there a skill that can...\", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.\n---\n\n# Find Skills\n\nThis skill helps you discover and install skills from the open agent skills ecosystem.\n\n## When to Use This Skill\n\nUse this skill when the user:\n\n- Asks \"how do I do X\" where X might be a common task with an existing skill\n- Says \"find a skill for X\" or \"is there a skill for X\"\n- Asks \"can you do X\" where X is a specialized capability\n- Expresses interest in extending agent capabilities\n- Wants to search for tools, templates, or workflows\n- Mentions they wish they had help with a specific domain (design, testing, deployment, etc.)\n\n## What is the Skills CLI?\n\nThe Skills CLI (`npx skills`) is the package manager for the open agent skills ecosystem. Skills are modular packages that extend agent capabilities with specialized knowledge, workflows, and tools.\n\n**Key commands:**\n\n- `npx skills find [query]` - Search for skills interactively or by keyword\n- `npx skills add <package>` - Install a skill from GitHub or other sources\n- `npx skills check` - Check for skill updates\n- `npx skills update` - Update all installed skills\n\n**Browse skills at:** https://skills.sh/\n\n## How to Help Users Find Skills\n\n### Step 1: Understand What They Need\n\nWhen a user asks for help with something, identify:\n\n1. The domain (e.g., React, testing, design, deployment)\n2. The specific task (e.g., writing tests, creating animations, reviewing PRs)\n3. Whether this is a common enough task that a skill likely exists\n\n### Step 2: Search for Skills\n\nRun the find command with a relevant query:\n\n```bash\nnpx skills find [query]\n```\n\nFor example:\n\n- User asks \"how do I make my React app faster?\" → `npx skills find react performance`\n- User asks \"can you help me with PR reviews?\" → `npx skills find pr review`\n- User asks \"I need to create a changelog\" → `npx skills find changelog`\n\nThe command will return results like:\n\n```\nInstall with npx skills add <owner/repo@skill>\n\nvercel-labs/agent-skills@vercel-react-best-practices\n└ https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices\n```\n\n### Step 3: Present Options to the User\n\nWhen you find relevant skills, present them to the user with:\n\n1. The skill name and what it does\n2. The install command they can run\n3. A link to learn more at skills.sh\n\nExample response:\n\n```\nI found a skill that might help! The \"vercel-react-best-practices\" skill provides\nReact and Next.js performance optimization guidelines from Vercel Engineering.\n\nTo install it:\nnpx skills add vercel-labs/agent-skills@vercel-react-best-practices\n\nLearn more: https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices\n```\n\n### Step 4: Offer to Install\n\nIf the user wants to proceed, you can install the skill for them:\n\n```bash\nnpx skills add <owner/repo@skill> -g -y\n```\n\nThe `-g` flag installs globally (user-level) and `-y` skips confirmation prompts.\n\n## Common Skill Categories\n\nWhen searching, consider these common categories:\n\n| Category        | Example Queries                          |\n| --------------- | ---------------------------------------- |\n| Web Development | react, nextjs, typescript, css, tailwind |\n| Testing         | testing, jest, playwright, e2e           |\n| DevOps          | deploy, docker, kubernetes, ci-cd        |\n| Documentation   | docs, readme, changelog, api-docs        |\n| Code Quality    | review, lint, refactor, best-practices   |\n| Design          | ui, ux, design-system, accessibility     |\n| Productivity    | workflow, automation, git                |\n\n## Tips for Effective Searches\n\n1. **Use specific keywords**: \"react testing\" is better than just \"testing\"\n2. **Try alternative terms**: If \"deploy\" doesn't work, try \"deployment\" or \"ci-cd\"\n3. **Check popular sources**: Many skills come from `vercel-labs/agent-skills` or `ComposioHQ/awesome-claude-skills`\n\n## When No Skills Are Found\n\nIf no relevant skills exist:\n\n1. Acknowledge that no existing skill was found\n2. Offer to help with the task directly using your general capabilities\n3. Suggest the user could create their own skill with `npx skills init`\n\nExample:\n\n```\nI searched for skills related to \"xyz\" but didn't find any matches.\nI can still help you with this task directly! Would you like me to proceed?\n\nIf this is something you do often, you could create your own skill:\nnpx skills init my-xyz-skill\n```\n\n<!-- File: .agents/skills/next-best-practices/async-patterns.md (Tokens: ~411 | Category: anthropic-skill) -->\n# Async Patterns\n\nIn Next.js 15+, `params`, `searchParams`, `cookies()`, and `headers()` are asynchronous.\n\n## Async Params and SearchParams\n\nAlways type them as `Promise<...>` and await them.\n\n### Pages and Layouts\n\n```tsx\ntype Props = { params: Promise<{ slug: string }> }\n\nexport default async function Page({ params }: Props) {\n  const { slug } = await params\n}\n```\n\n### Route Handlers\n\n```tsx\nexport async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n}\n```\n\n### SearchParams\n\n```tsx\ntype Props = {\n  params: Promise<{ slug: string }>\n  searchParams: Promise<{ query?: string }>\n}\n\nexport default async function Page({ params, searchParams }: Props) {\n  const { slug } = await params\n  const { query } = await searchParams\n}\n```\n\n### Synchronous Components\n\nUse `React.use()` for non-async components:\n\n```tsx\nimport { use } from 'react'\n\ntype Props = { params: Promise<{ slug: string }> }\n\nexport default function Page({ params }: Props) {\n  const { slug } = use(params)\n}\n```\n\n### generateMetadata\n\n```tsx\ntype Props = { params: Promise<{ slug: string }> }\n\nexport async function generateMetadata({ params }: Props): Promise<Metadata> {\n  const { slug } = await params\n  return { title: slug }\n}\n```\n\n## Async Cookies and Headers\n\n```tsx\nimport { cookies, headers } from 'next/headers'\n\nexport default async function Page() {\n  const cookieStore = await cookies()\n  const headersList = await headers()\n\n  const theme = cookieStore.get('theme')\n  const userAgent = headersList.get('user-agent')\n}\n```\n\n## Migration Codemod\n\n```bash\nnpx @next/codemod@latest next-async-request-api .\n```\n\n<!-- File: .agents/skills/next-best-practices/bundling.md (Tokens: ~1131 | Category: anthropic-skill) -->\n# Bundling\n\nFix common bundling issues with third-party packages.\n\n## Server-Incompatible Packages\n\nSome packages use browser APIs (`window`, `document`, `localStorage`) and fail in Server Components.\n\n### Error Signs\n\n```\nReferenceError: window is not defined\nReferenceError: document is not defined\nReferenceError: localStorage is not defined\nModule not found: Can't resolve 'fs'\n```\n\n### Solution 1: Mark as Client-Only\n\nIf the package is only needed on client:\n\n```tsx\n// Bad: Fails - package uses window\nimport SomeChart from 'some-chart-library'\n\nexport default function Page() {\n  return <SomeChart />\n}\n\n// Good: Use dynamic import with ssr: false\nimport dynamic from 'next/dynamic'\n\nconst SomeChart = dynamic(() => import('some-chart-library'), {\n  ssr: false,\n})\n\nexport default function Page() {\n  return <SomeChart />\n}\n```\n\n### Solution 2: Externalize from Server Bundle\n\nFor packages that should run on server but have bundling issues:\n\n```js\n// next.config.js\nmodule.exports = {\n  serverExternalPackages: ['problematic-package'],\n}\n```\n\nUse this for:\n\n- Packages with native bindings (sharp, bcrypt)\n- Packages that don't bundle well (some ORMs)\n- Packages with circular dependencies\n\n### Solution 3: Client Component Wrapper\n\nWrap the entire usage in a client component:\n\n```tsx\n// components/ChartWrapper.tsx\n'use client'\n\nimport { Chart } from 'chart-library'\n\nexport function ChartWrapper(props) {\n  return <Chart {...props} />\n}\n\n// app/page.tsx (server component)\nimport { ChartWrapper } from '@/components/ChartWrapper'\n\nexport default function Page() {\n  return <ChartWrapper data={data} />\n}\n```\n\n## CSS Imports\n\nImport CSS files instead of using `<link>` tags. Next.js handles bundling and optimization.\n\n```tsx\n// Bad: Manual link tag\n;<link rel=\"stylesheet\" href=\"/styles.css\" />\n\n// Good: Import CSS\nimport './styles.css'\n\n// Good: CSS Modules\nimport styles from './Button.module.css'\n```\n\n## Polyfills\n\nNext.js includes common polyfills automatically. Don't load redundant ones from polyfill.io or similar CDNs.\n\nAlready included: `Array.from`, `Object.assign`, `Promise`, `fetch`, `Map`, `Set`, `Symbol`, `URLSearchParams`, and 50+ others.\n\n```tsx\n// Bad: Redundant polyfills\n<script src=\"https://polyfill.io/v3/polyfill.min.js?features=fetch,Promise,Array.from\" />\n\n// Good: Next.js includes these automatically\n```\n\n## ESM/CommonJS Issues\n\n### Error Signs\n\n```\nSyntaxError: Cannot use import statement outside a module\nError: require() of ES Module\nModule not found: ESM packages need to be imported\n```\n\n### Solution: Transpile Package\n\n```js\n// next.config.js\nmodule.exports = {\n  transpilePackages: ['some-esm-package', 'another-package'],\n}\n```\n\n## Common Problematic Packages\n\n| Package         | Issue           | Solution                                                        |\n| --------------- | --------------- | --------------------------------------------------------------- |\n| `sharp`         | Native bindings | `serverExternalPackages: ['sharp']`                             |\n| `bcrypt`        | Native bindings | `serverExternalPackages: ['bcrypt']` or use `bcryptjs`          |\n| `canvas`        | Native bindings | `serverExternalPackages: ['canvas']`                            |\n| `recharts`      | Uses window     | `dynamic(() => import('recharts'), { ssr: false })`             |\n| `react-quill`   | Uses document   | `dynamic(() => import('react-quill'), { ssr: false })`          |\n| `mapbox-gl`     | Uses window     | `dynamic(() => import('mapbox-gl'), { ssr: false })`            |\n| `monaco-editor` | Uses window     | `dynamic(() => import('@monaco-editor/react'), { ssr: false })` |\n| `lottie-web`    | Uses document   | `dynamic(() => import('lottie-react'), { ssr: false })`         |\n\n## Bundle Analysis\n\nAnalyze bundle size with the built-in analyzer (Next.js 16.1+):\n\n```bash\nnext experimental-analyze\n```\n\nThis opens an interactive UI to:\n\n- Filter by route, environment (client/server), and type\n- Inspect module sizes and import chains\n- View treemap visualization\n\nSave output for comparison:\n\n```bash\nnext experimental-analyze --output\n# Output saved to .next/diagnostics/analyze\n```\n\nReference: https://nextjs.org/docs/app/guides/package-bundling\n\n## Migrating from Webpack to Turbopack\n\nTurbopack is the default bundler in Next.js 15+. If you have custom webpack config, migrate to Turbopack-compatible alternatives:\n\n```js\n// next.config.js\nmodule.exports = {\n  // Good: Works with Turbopack\n  serverExternalPackages: ['package'],\n  transpilePackages: ['package'],\n\n  // Bad: Webpack-only - migrate away from this\n  webpack: (config) => {\n    // custom webpack config\n  },\n}\n```\n\nReference: https://nextjs.org/docs/app/building-your-application/upgrading/from-webpack-to-turbopack\n\n<!-- File: .agents/skills/next-best-practices/data-patterns.md (Tokens: ~1637 | Category: anthropic-skill) -->\n# Data Patterns\n\nChoose the right data fetching pattern for each use case.\n\n## Decision Tree\n\n```\nNeed to fetch data?\n├── From a Server Component?\n│   └── Use: Fetch directly (no API needed)\n│\n├── From a Client Component?\n│   ├── Is it a mutation (POST/PUT/DELETE)?\n│   │   └── Use: Server Action\n│   └── Is it a read (GET)?\n│       └── Use: Route Handler OR pass from Server Component\n│\n├── Need external API access (webhooks, third parties)?\n│   └── Use: Route Handler\n│\n└── Need REST API for mobile app / external clients?\n    └── Use: Route Handler\n```\n\n## Pattern 1: Server Components (Preferred for Reads)\n\nFetch data directly in Server Components - no API layer needed.\n\n```tsx\n// app/users/page.tsx\nasync function UsersPage() {\n  // Direct database access - no API round-trip\n  const users = await db.user.findMany()\n\n  // Or fetch from external API\n  const posts = await fetch('https://api.example.com/posts').then((r) => r.json())\n\n  return (\n    <ul>\n      {users.map((user) => (\n        <li key={user.id}>{user.name}</li>\n      ))}\n    </ul>\n  )\n}\n```\n\n**Benefits**:\n\n- No API to maintain\n- No client-server waterfall\n- Secrets stay on server\n- Direct database access\n\n## Pattern 2: Server Actions (Preferred for Mutations)\n\nServer Actions are the recommended way to handle mutations.\n\n```tsx\n// app/actions.ts\n'use server'\n\nimport { revalidatePath } from 'next/cache'\n\nexport async function createPost(formData: FormData) {\n  const title = formData.get('title') as string\n\n  await db.post.create({ data: { title } })\n\n  revalidatePath('/posts')\n}\n\nexport async function deletePost(id: string) {\n  await db.post.delete({ where: { id } })\n\n  revalidateTag('posts')\n}\n```\n\n```tsx\n// app/posts/new/page.tsx\nimport { createPost } from '@/app/actions'\n\nexport default function NewPost() {\n  return (\n    <form action={createPost}>\n      <input name=\"title\" required />\n      <button type=\"submit\">Create</button>\n    </form>\n  )\n}\n```\n\n**Benefits**:\n\n- End-to-end type safety\n- Progressive enhancement (works without JS)\n- Automatic request handling\n- Integrated with React transitions\n\n**Constraints**:\n\n- POST only (no GET caching semantics)\n- Internal use only (no external access)\n- Cannot return non-serializable data\n\n## Pattern 3: Route Handlers (APIs)\n\nUse Route Handlers when you need a REST API.\n\n```tsx\n// app/api/posts/route.ts\nimport { NextRequest, NextResponse } from 'next/server'\n\n// GET is cacheable\nexport async function GET(request: NextRequest) {\n  const posts = await db.post.findMany()\n  return NextResponse.json(posts)\n}\n\n// POST for mutations\nexport async function POST(request: NextRequest) {\n  const body = await request.json()\n  const post = await db.post.create({ data: body })\n  return NextResponse.json(post, { status: 201 })\n}\n```\n\n**When to use**:\n\n- External API access (mobile apps, third parties)\n- Webhooks from external services\n- GET endpoints that need HTTP caching\n- OpenAPI/Swagger documentation needed\n\n**When NOT to use**:\n\n- Internal data fetching (use Server Components)\n- Mutations from your UI (use Server Actions)\n\n## Avoiding Data Waterfalls\n\n### Problem: Sequential Fetches\n\n```tsx\n// Bad: Sequential waterfalls\nasync function Dashboard() {\n  const user = await getUser() // Wait...\n  const posts = await getPosts() // Then wait...\n  const comments = await getComments() // Then wait...\n\n  return <div>...</div>\n}\n```\n\n### Solution 1: Parallel Fetching with Promise.all\n\n```tsx\n// Good: Parallel fetching\nasync function Dashboard() {\n  const [user, posts, comments] = await Promise.all([getUser(), getPosts(), getComments()])\n\n  return <div>...</div>\n}\n```\n\n### Solution 2: Streaming with Suspense\n\n```tsx\n// Good: Show content progressively\nimport { Suspense } from 'react'\n\nasync function Dashboard() {\n  return (\n    <div>\n      <Suspense fallback={<UserSkeleton />}>\n        <UserSection />\n      </Suspense>\n      <Suspense fallback={<PostsSkeleton />}>\n        <PostsSection />\n      </Suspense>\n    </div>\n  )\n}\n\nasync function UserSection() {\n  const user = await getUser() // Fetches independently\n  return <div>{user.name}</div>\n}\n\nasync function PostsSection() {\n  const posts = await getPosts() // Fetches independently\n  return <PostList posts={posts} />\n}\n```\n\n### Solution 3: Preload Pattern\n\n```tsx\n// lib/data.ts\nimport { cache } from 'react'\n\nexport const getUser = cache(async (id: string) => {\n  return db.user.findUnique({ where: { id } })\n})\n\nexport const preloadUser = (id: string) => {\n  void getUser(id) // Fire and forget\n}\n```\n\n```tsx\n// app/user/[id]/page.tsx\nimport { getUser, preloadUser } from '@/lib/data'\n\nexport default async function UserPage({ params }) {\n  const { id } = await params\n\n  // Start fetching early\n  preloadUser(id)\n\n  // Do other work...\n\n  // Data likely ready by now\n  const user = await getUser(id)\n  return <div>{user.name}</div>\n}\n```\n\n## Client Component Data Fetching\n\nWhen Client Components need data:\n\n### Option 1: Pass from Server Component (Preferred)\n\n```tsx\n// Server Component\nasync function Page() {\n  const data = await fetchData()\n  return <ClientComponent initialData={data} />\n}\n\n// Client Component\n;('use client')\nfunction ClientComponent({ initialData }) {\n  const [data, setData] = useState(initialData)\n  // ...\n}\n```\n\n### Option 2: Fetch on Mount (When Necessary)\n\n```tsx\n'use client'\nimport { useEffect, useState } from 'react'\n\nfunction ClientComponent() {\n  const [data, setData] = useState(null)\n\n  useEffect(() => {\n    fetch('/api/data')\n      .then((r) => r.json())\n      .then(setData)\n  }, [])\n\n  if (!data) return <Loading />\n  return <div>{data.value}</div>\n}\n```\n\n### Option 3: Server Action for Reads (Works But Not Ideal)\n\nServer Actions can be called from Client Components for reads, but this is not their intended purpose:\n\n```tsx\n'use client'\nimport { getData } from './actions'\nimport { useEffect, useState } from 'react'\n\nfunction ClientComponent() {\n  const [data, setData] = useState(null)\n\n  useEffect(() => {\n    getData().then(setData)\n  }, [])\n\n  return <div>{data?.value}</div>\n}\n```\n\n**Note**: Server Actions always use POST, so no HTTP caching. Prefer Route Handlers for cacheable reads.\n\n## Quick Reference\n\n| Pattern                | Use Case                    | HTTP Method | Caching              |\n| ---------------------- | --------------------------- | ----------- | -------------------- |\n| Server Component fetch | Internal reads              | Any         | Full Next.js caching |\n| Server Action          | Mutations, form submissions | POST only   | No                   |\n| Route Handler          | External APIs, webhooks     | Any         | GET can be cached    |\n| Client fetch to API    | Client-side reads           | Any         | HTTP cache headers   |\n\n<!-- File: .agents/skills/next-best-practices/debug-tricks.md (Tokens: ~781 | Category: anthropic-skill) -->\n# Debug Tricks\n\nTricks to speed up debugging Next.js applications.\n\n## MCP Endpoint (Dev Server)\n\nNext.js exposes a `/_next/mcp` endpoint in development for AI-assisted debugging via MCP (Model Context Protocol).\n\n- **Next.js 16+**: Enabled by default, use `next-devtools-mcp`\n- **Next.js < 16**: Requires `experimental.mcpServer: true` in next.config.js\n\nReference: https://nextjs.org/docs/app/guides/mcp\n\n**Important**: Find the actual port of the running Next.js dev server (check terminal output or `package.json` scripts). Don't assume port 3000.\n\n### Request Format\n\nThe endpoint uses JSON-RPC 2.0 over HTTP POST:\n\n```bash\ncurl -X POST http://localhost:<port>/_next/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json, text/event-stream\" \\\n  -d '{\n    \"jsonrpc\": \"2.0\",\n    \"id\": \"1\",\n    \"method\": \"tools/call\",\n    \"params\": {\n      \"name\": \"<tool-name>\",\n      \"arguments\": {}\n    }\n  }'\n```\n\n### Available Tools\n\n#### `get_errors`\n\nGet current errors from dev server (build errors, runtime errors with source-mapped stacks):\n\n```json\n{ \"name\": \"get_errors\", \"arguments\": {} }\n```\n\n#### `get_routes`\n\nDiscover all routes by scanning filesystem:\n\n```json\n{ \"name\": \"get_routes\", \"arguments\": {} }\n// Optional: { \"name\": \"get_routes\", \"arguments\": { \"routerType\": \"app\" } }\n```\n\nReturns: `{ \"appRouter\": [\"/\", \"/api/users/[id]\", ...], \"pagesRouter\": [...] }`\n\n#### `get_project_metadata`\n\nGet project path and dev server URL:\n\n```json\n{ \"name\": \"get_project_metadata\", \"arguments\": {} }\n```\n\nReturns: `{ \"projectPath\": \"/path/to/project\", \"devServerUrl\": \"http://localhost:3000\" }`\n\n#### `get_page_metadata`\n\nGet runtime metadata about current page render (requires active browser session):\n\n```json\n{ \"name\": \"get_page_metadata\", \"arguments\": {} }\n```\n\nReturns segment trie data showing layouts, boundaries, and page components.\n\n#### `get_logs`\n\nGet path to Next.js development log file:\n\n```json\n{ \"name\": \"get_logs\", \"arguments\": {} }\n```\n\nReturns path to `<distDir>/logs/next-development.log`\n\n#### `get_server_action_by_id`\n\nLocate a Server Action by ID:\n\n```json\n{ \"name\": \"get_server_action_by_id\", \"arguments\": { \"actionId\": \"<action-id>\" } }\n```\n\n### Example: Get Errors\n\n```bash\ncurl -X POST http://localhost:<port>/_next/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json, text/event-stream\" \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"tools/call\",\"params\":{\"name\":\"get_errors\",\"arguments\":{}}}'\n```\n\n## Rebuild Specific Routes (Next.js 16+)\n\nUse `--debug-build-paths` to rebuild only specific routes instead of the entire app:\n\n```bash\n# Rebuild a specific route\nnext build --debug-build-paths \"/dashboard\"\n\n# Rebuild routes matching a glob\nnext build --debug-build-paths \"/api/*\"\n\n# Dynamic routes\nnext build --debug-build-paths \"/blog/[slug]\"\n```\n\nUse this to:\n\n- Quickly verify a build fix without full rebuild\n- Debug static generation issues for specific pages\n- Iterate faster on build errors\n\n<!-- File: .agents/skills/next-best-practices/directives.md (Tokens: ~351 | Category: anthropic-skill) -->\n# Directives\n\n## React Directives\n\nThese are React directives, not Next.js specific.\n\n### `'use client'`\n\nMarks a component as a Client Component. Required for:\n\n- React hooks (`useState`, `useEffect`, etc.)\n- Event handlers (`onClick`, `onChange`)\n- Browser APIs (`window`, `localStorage`)\n\n```tsx\n'use client'\n\nimport { useState } from 'react'\n\nexport function Counter() {\n  const [count, setCount] = useState(0)\n  return <button onClick={() => setCount(count + 1)}>{count}</button>\n}\n```\n\nReference: https://react.dev/reference/rsc/use-client\n\n### `'use server'`\n\nMarks a function as a Server Action. Can be passed to Client Components.\n\n```tsx\n'use server'\n\nexport async function submitForm(formData: FormData) {\n  // Runs on server\n}\n```\n\nOr inline within a Server Component:\n\n```tsx\nexport default function Page() {\n  async function submit() {\n    'use server'\n    // Runs on server\n  }\n  return <form action={submit}>...</form>\n}\n```\n\nReference: https://react.dev/reference/rsc/use-server\n\n---\n\n## Next.js Directive\n\n### `'use cache'`\n\nMarks a function or component for caching. Part of Next.js Cache Components.\n\n```tsx\n'use cache'\n\nexport async function getCachedData() {\n  return await fetchData()\n}\n```\n\nRequires `cacheComponents: true` in `next.config.ts`.\n\nFor detailed usage including cache profiles, `cacheLife()`, `cacheTag()`, and `updateTag()`, see the `next-cache-components` skill.\n\nReference: https://nextjs.org/docs/app/api-reference/directives/use-cache\n\n<!-- File: .agents/skills/next-best-practices/error-handling.md (Tokens: ~1185 | Category: anthropic-skill) -->\n# Error Handling\n\nHandle errors gracefully in Next.js applications.\n\nReference: https://nextjs.org/docs/app/getting-started/error-handling\n\n## Error Boundaries\n\n### `error.tsx`\n\nCatches errors in a route segment and its children:\n\n```tsx\n'use client'\n\nexport default function Error({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {\n  return (\n    <div>\n      <h2>Something went wrong!</h2>\n      <button onClick={() => reset()}>Try again</button>\n    </div>\n  )\n}\n```\n\n**Important:** `error.tsx` must be a Client Component.\n\n### `global-error.tsx`\n\nCatches errors in root layout:\n\n```tsx\n'use client'\n\nexport default function GlobalError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {\n  return (\n    <html>\n      <body>\n        <h2>Something went wrong!</h2>\n        <button onClick={() => reset()}>Try again</button>\n      </body>\n    </html>\n  )\n}\n```\n\n**Important:** Must include `<html>` and `<body>` tags.\n\n## Server Actions: Navigation API Gotcha\n\n**Do NOT wrap navigation APIs in try-catch.** They throw special errors that Next.js handles internally.\n\nReference: https://nextjs.org/docs/app/api-reference/functions/redirect#behavior\n\n```tsx\n'use server'\n\nimport { redirect } from 'next/navigation'\nimport { notFound } from 'next/navigation'\n\n// Bad: try-catch catches the navigation \"error\"\nasync function createPost(formData: FormData) {\n  try {\n    const post = await db.post.create({ ... })\n    redirect(`/posts/${post.id}`)  // This throws!\n  } catch (error) {\n    // redirect() throw is caught here - navigation fails!\n    return { error: 'Failed to create post' }\n  }\n}\n\n// Good: Call navigation APIs outside try-catch\nasync function createPost(formData: FormData) {\n  let post\n  try {\n    post = await db.post.create({ ... })\n  } catch (error) {\n    return { error: 'Failed to create post' }\n  }\n  redirect(`/posts/${post.id}`)  // Outside try-catch\n}\n\n// Good: Re-throw navigation errors\nasync function createPost(formData: FormData) {\n  try {\n    const post = await db.post.create({ ... })\n    redirect(`/posts/${post.id}`)\n  } catch (error) {\n    if (error instanceof Error && error.message === 'NEXT_REDIRECT') {\n      throw error  // Re-throw navigation errors\n    }\n    return { error: 'Failed to create post' }\n  }\n}\n```\n\nSame applies to:\n\n- `redirect()` - 307 temporary redirect\n- `permanentRedirect()` - 308 permanent redirect\n- `notFound()` - 404 not found\n- `forbidden()` - 403 forbidden\n- `unauthorized()` - 401 unauthorized\n\nUse `unstable_rethrow()` to re-throw these errors in catch blocks:\n\n```tsx\nimport { unstable_rethrow } from 'next/navigation'\n\nasync function action() {\n  try {\n    // ...\n    redirect('/success')\n  } catch (error) {\n    unstable_rethrow(error) // Re-throws Next.js internal errors\n    return { error: 'Something went wrong' }\n  }\n}\n```\n\n## Redirects\n\n```tsx\nimport { redirect, permanentRedirect } from 'next/navigation'\n\n// 307 Temporary - use for most cases\nredirect('/new-path')\n\n// 308 Permanent - use for URL migrations (cached by browsers)\npermanentRedirect('/new-url')\n```\n\n## Auth Errors\n\nTrigger auth-related error pages:\n\n```tsx\nimport { forbidden, unauthorized } from 'next/navigation'\n\nasync function Page() {\n  const session = await getSession()\n\n  if (!session) {\n    unauthorized() // Renders unauthorized.tsx (401)\n  }\n\n  if (!session.hasAccess) {\n    forbidden() // Renders forbidden.tsx (403)\n  }\n\n  return <Dashboard />\n}\n```\n\nCreate corresponding error pages:\n\n```tsx\n// app/forbidden.tsx\nexport default function Forbidden() {\n  return <div>You don't have access to this resource</div>\n}\n\n// app/unauthorized.tsx\nexport default function Unauthorized() {\n  return <div>Please log in to continue</div>\n}\n```\n\n## Not Found\n\n### `not-found.tsx`\n\nCustom 404 page for a route segment:\n\n```tsx\nexport default function NotFound() {\n  return (\n    <div>\n      <h2>Not Found</h2>\n      <p>Could not find the requested resource</p>\n    </div>\n  )\n}\n```\n\n### Triggering Not Found\n\n```tsx\nimport { notFound } from 'next/navigation'\n\nexport default async function Page({ params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n  const post = await getPost(id)\n\n  if (!post) {\n    notFound() // Renders closest not-found.tsx\n  }\n\n  return <div>{post.title}</div>\n}\n```\n\n## Error Hierarchy\n\nErrors bubble up to the nearest error boundary:\n\n```\napp/\n├── error.tsx           # Catches errors from all children\n├── blog/\n│   ├── error.tsx       # Catches errors in /blog/*\n│   └── [slug]/\n│       ├── error.tsx   # Catches errors in /blog/[slug]\n│       └── page.tsx\n└── layout.tsx          # Errors here go to global-error.tsx\n```\n\n<!-- File: .agents/skills/next-best-practices/file-conventions.md (Tokens: ~979 | Category: anthropic-skill) -->\n# File Conventions\n\nNext.js App Router uses file-based routing with special file conventions.\n\n## Project Structure\n\nReference: https://nextjs.org/docs/app/getting-started/project-structure\n\n```\napp/\n├── layout.tsx          # Root layout (required)\n├── page.tsx            # Home page (/)\n├── loading.tsx         # Loading UI\n├── error.tsx           # Error UI\n├── not-found.tsx       # 404 UI\n├── global-error.tsx    # Global error UI\n├── route.ts            # API endpoint\n├── template.tsx        # Re-rendered layout\n├── default.tsx         # Parallel route fallback\n├── blog/\n│   ├── page.tsx        # /blog\n│   └── [slug]/\n│       └── page.tsx    # /blog/:slug\n└── (group)/            # Route group (no URL impact)\n    └── page.tsx\n```\n\n## Special Files\n\n| File            | Purpose                                  |\n| --------------- | ---------------------------------------- |\n| `page.tsx`      | UI for a route segment                   |\n| `layout.tsx`    | Shared UI for segment and children       |\n| `loading.tsx`   | Loading UI (Suspense boundary)           |\n| `error.tsx`     | Error UI (Error boundary)                |\n| `not-found.tsx` | 404 UI                                   |\n| `route.ts`      | API endpoint                             |\n| `template.tsx`  | Like layout but re-renders on navigation |\n| `default.tsx`   | Fallback for parallel routes             |\n\n## Route Segments\n\n```\napp/\n├── blog/               # Static segment: /blog\n├── [slug]/             # Dynamic segment: /:slug\n├── [...slug]/          # Catch-all: /a/b/c\n├── [[...slug]]/        # Optional catch-all: / or /a/b/c\n└── (marketing)/        # Route group (ignored in URL)\n```\n\n## Parallel Routes\n\n```\napp/\n├── @analytics/\n│   └── page.tsx\n├── @sidebar/\n│   └── page.tsx\n└── layout.tsx          # Receives { analytics, sidebar } as props\n```\n\n## Intercepting Routes\n\n```\napp/\n├── feed/\n│   └── page.tsx\n├── @modal/\n│   └── (.)photo/[id]/  # Intercepts /photo/[id] from /feed\n│       └── page.tsx\n└── photo/[id]/\n    └── page.tsx\n```\n\nConventions:\n\n- `(.)` - same level\n- `(..)` - one level up\n- `(..)(..)` - two levels up\n- `(...)` - from root\n\n## Private Folders\n\n```\napp/\n├── _components/        # Private folder (not a route)\n│   └── Button.tsx\n└── page.tsx\n```\n\nPrefix with `_` to exclude from routing.\n\n## Middleware / Proxy\n\n### Next.js 14-15: `middleware.ts`\n\n```ts\n// middleware.ts (root of project)\nimport { NextResponse } from 'next/server'\nimport type { NextRequest } from 'next/server'\n\nexport function middleware(request: NextRequest) {\n  // Auth, redirects, rewrites, etc.\n  return NextResponse.next()\n}\n\nexport const config = {\n  matcher: ['/dashboard/:path*', '/api/:path*'],\n}\n```\n\n### Next.js 16+: `proxy.ts`\n\nRenamed for clarity - same capabilities, different names:\n\n```ts\n// proxy.ts (root of project)\nimport { NextResponse } from 'next/server'\nimport type { NextRequest } from 'next/server'\n\nexport function proxy(request: NextRequest) {\n  // Same logic as middleware\n  return NextResponse.next()\n}\n\nexport const proxyConfig = {\n  matcher: ['/dashboard/:path*', '/api/:path*'],\n}\n```\n\n| Version | File            | Export         | Config        |\n| ------- | --------------- | -------------- | ------------- |\n| v14-15  | `middleware.ts` | `middleware()` | `config`      |\n| v16+    | `proxy.ts`      | `proxy()`      | `proxyConfig` |\n\n**Migration**: Run `npx @next/codemod@latest upgrade` to auto-rename.\n\n## File Conventions Reference\n\nReference: https://nextjs.org/docs/app/api-reference/file-conventions\n\n<!-- File: .agents/skills/next-best-practices/font.md (Tokens: ~1316 | Category: anthropic-skill) -->\n# Font Optimization\n\nUse `next/font` for automatic font optimization with zero layout shift.\n\n## Google Fonts\n\n```tsx\n// app/layout.tsx\nimport { Inter } from 'next/font/google'\n\nconst inter = Inter({ subsets: ['latin'] })\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\" className={inter.className}>\n      <body>{children}</body>\n    </html>\n  )\n}\n```\n\n## Multiple Fonts\n\n```tsx\nimport { Inter, Roboto_Mono } from 'next/font/google'\n\nconst inter = Inter({\n  subsets: ['latin'],\n  variable: '--font-inter',\n})\n\nconst robotoMono = Roboto_Mono({\n  subsets: ['latin'],\n  variable: '--font-roboto-mono',\n})\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\" className={`${inter.variable} ${robotoMono.variable}`}>\n      <body>{children}</body>\n    </html>\n  )\n}\n```\n\nUse in CSS:\n\n```css\nbody {\n  font-family: var(--font-inter);\n}\n\ncode {\n  font-family: var(--font-roboto-mono);\n}\n```\n\n## Font Weights and Styles\n\n```tsx\n// Single weight\nconst inter = Inter({\n  subsets: ['latin'],\n  weight: '400',\n})\n\n// Multiple weights\nconst inter = Inter({\n  subsets: ['latin'],\n  weight: ['400', '500', '700'],\n})\n\n// Variable font (recommended) - includes all weights\nconst inter = Inter({\n  subsets: ['latin'],\n  // No weight needed - variable fonts support all weights\n})\n\n// With italic\nconst inter = Inter({\n  subsets: ['latin'],\n  style: ['normal', 'italic'],\n})\n```\n\n## Local Fonts\n\n```tsx\nimport localFont from 'next/font/local'\n\nconst myFont = localFont({\n  src: './fonts/MyFont.woff2',\n})\n\n// Multiple files for different weights\nconst myFont = localFont({\n  src: [\n    {\n      path: './fonts/MyFont-Regular.woff2',\n      weight: '400',\n      style: 'normal',\n    },\n    {\n      path: './fonts/MyFont-Bold.woff2',\n      weight: '700',\n      style: 'normal',\n    },\n  ],\n})\n\n// Variable font\nconst myFont = localFont({\n  src: './fonts/MyFont-Variable.woff2',\n  variable: '--font-my-font',\n})\n```\n\n## Tailwind CSS Integration\n\n```tsx\n// app/layout.tsx\nimport { Inter } from 'next/font/google'\n\nconst inter = Inter({\n  subsets: ['latin'],\n  variable: '--font-inter',\n})\n\nexport default function RootLayout({ children }) {\n  return (\n    <html lang=\"en\" className={inter.variable}>\n      <body>{children}</body>\n    </html>\n  )\n}\n```\n\n```js\n// tailwind.config.js\nmodule.exports = {\n  theme: {\n    extend: {\n      fontFamily: {\n        sans: ['var(--font-inter)'],\n      },\n    },\n  },\n}\n```\n\n## Preloading Subsets\n\nOnly load needed character subsets:\n\n```tsx\n// Latin only (most common)\nconst inter = Inter({ subsets: ['latin'] })\n\n// Multiple subsets\nconst inter = Inter({ subsets: ['latin', 'latin-ext', 'cyrillic'] })\n```\n\n## Display Strategy\n\nControl font loading behavior:\n\n```tsx\nconst inter = Inter({\n  subsets: ['latin'],\n  display: 'swap', // Default - shows fallback, swaps when loaded\n})\n\n// Options:\n// 'auto' - browser decides\n// 'block' - short block period, then swap\n// 'swap' - immediate fallback, swap when ready (recommended)\n// 'fallback' - short block, short swap, then fallback\n// 'optional' - short block, no swap (use if font is optional)\n```\n\n## Don't Use Manual Font Links\n\nAlways use `next/font` instead of `<link>` tags for Google Fonts.\n\n```tsx\n// Bad: Manual link tag (blocks rendering, no optimization)\n<link href=\"https://fonts.googleapis.com/css2?family=Inter\" rel=\"stylesheet\" />\n\n// Bad: Missing display and preconnect\n<link href=\"https://fonts.googleapis.com/css2?family=Inter\" rel=\"stylesheet\" />\n\n// Good: Use next/font (self-hosted, zero layout shift)\nimport { Inter } from 'next/font/google'\n\nconst inter = Inter({ subsets: ['latin'] })\n```\n\n## Common Mistakes\n\n```tsx\n// Bad: Importing font in every component\n// components/Button.tsx\nimport { Inter } from 'next/font/google'\nconst inter = Inter({ subsets: ['latin'] }) // Creates new instance each time!\n\n// Good: Import once in layout, use CSS variable\n// app/layout.tsx\nconst inter = Inter({ subsets: ['latin'], variable: '--font-inter' })\n\n// Bad: Using @import in CSS (blocks rendering)\n/* globals.css */\n@import url('https://fonts.googleapis.com/css2?family=Inter');\n\n// Good: Use next/font (self-hosted, no network request)\nimport { Inter } from 'next/font/google'\n\n// Bad: Loading all weights when only using a few\nconst inter = Inter({ subsets: ['latin'] }) // Loads all weights\n\n// Good: Specify only needed weights (for non-variable fonts)\nconst inter = Inter({ subsets: ['latin'], weight: ['400', '700'] })\n\n// Bad: Missing subset - loads all characters\nconst inter = Inter({})\n\n// Good: Always specify subset\nconst inter = Inter({ subsets: ['latin'] })\n```\n\n## Font in Specific Components\n\n```tsx\n// For component-specific fonts, export from a shared file\n// lib/fonts.ts\nimport { Inter, Playfair_Display } from 'next/font/google'\n\nexport const inter = Inter({ subsets: ['latin'], variable: '--font-inter' })\nexport const playfair = Playfair_Display({ subsets: ['latin'], variable: '--font-playfair' })\n\n// components/Heading.tsx\nimport { playfair } from '@/lib/fonts'\n\nexport function Heading({ children }) {\n  return <h1 className={playfair.className}>{children}</h1>\n}\n```\n\n<!-- File: .agents/skills/next-best-practices/functions.md (Tokens: ~1105 | Category: anthropic-skill) -->\n# Functions\n\nNext.js function APIs.\n\nReference: https://nextjs.org/docs/app/api-reference/functions\n\n## Navigation Hooks (Client)\n\n| Hook                        | Purpose                                                        | Reference                                                                                |\n| --------------------------- | -------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |\n| `useRouter`                 | Programmatic navigation (`push`, `replace`, `back`, `refresh`) | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-router)                   |\n| `usePathname`               | Get current pathname                                           | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-pathname)                 |\n| `useSearchParams`           | Read URL search parameters                                     | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-search-params)            |\n| `useParams`                 | Access dynamic route parameters                                | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-params)                   |\n| `useSelectedLayoutSegment`  | Active child segment (one level)                               | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-selected-layout-segment)  |\n| `useSelectedLayoutSegments` | All active segments below layout                               | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-selected-layout-segments) |\n| `useLinkStatus`             | Check link prefetch status                                     | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-link-status)              |\n| `useReportWebVitals`        | Report Core Web Vitals metrics                                 | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-report-web-vitals)        |\n\n## Server Functions\n\n| Function     | Purpose                                      | Reference                                                              |\n| ------------ | -------------------------------------------- | ---------------------------------------------------------------------- |\n| `cookies`    | Read/write cookies                           | [Docs](https://nextjs.org/docs/app/api-reference/functions/cookies)    |\n| `headers`    | Read request headers                         | [Docs](https://nextjs.org/docs/app/api-reference/functions/headers)    |\n| `draftMode`  | Enable preview of unpublished CMS content    | [Docs](https://nextjs.org/docs/app/api-reference/functions/draft-mode) |\n| `after`      | Run code after response finishes streaming   | [Docs](https://nextjs.org/docs/app/api-reference/functions/after)      |\n| `connection` | Wait for connection before dynamic rendering | [Docs](https://nextjs.org/docs/app/api-reference/functions/connection) |\n| `userAgent`  | Parse User-Agent header                      | [Docs](https://nextjs.org/docs/app/api-reference/functions/userAgent)  |\n\n## Generate Functions\n\n| Function                | Purpose                                 | Reference                                                                           |\n| ----------------------- | --------------------------------------- | ----------------------------------------------------------------------------------- |\n| `generateStaticParams`  | Pre-render dynamic routes at build time | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-static-params)  |\n| `generateMetadata`      | Dynamic metadata                        | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-metadata)       |\n| `generateViewport`      | Dynamic viewport config                 | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-viewport)       |\n| `generateSitemaps`      | Multiple sitemaps for large sites       | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-sitemaps)       |\n| `generateImageMetadata` | Multiple OG images per route            | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-image-metadata) |\n\n## Request/Response\n\n| Function        | Purpose                        | Reference                                                                  |\n| --------------- | ------------------------------ | -------------------------------------------------------------------------- |\n| `NextRequest`   | Extended Request with helpers  | [Docs](https://nextjs.org/docs/app/api-reference/functions/next-request)   |\n| `NextResponse`  | Extended Response with helpers | [Docs](https://nextjs.org/docs/app/api-reference/functions/next-response)  |\n| `ImageResponse` | Generate OG images             | [Docs](https://nextjs.org/docs/app/api-reference/functions/image-response) |\n\n## Common Examples\n\n### Navigation\n\nUse `next/link` for internal navigation instead of `<a>` tags.\n\n```tsx\n// Bad: Plain anchor tag\n;<a href=\"/about\">About</a>\n\n// Good: Next.js Link\nimport Link from 'next/link'\n;<Link href=\"/about\">About</Link>\n```\n\nActive link styling:\n\n```tsx\n'use client'\n\nimport Link from 'next/link'\nimport { usePathname } from 'next/navigation'\n\nexport function NavLink({ href, children }) {\n  const pathname = usePathname()\n\n  return (\n    <Link href={href} className={pathname === href ? 'active' : ''}>\n      {children}\n    </Link>\n  )\n}\n```\n\n### Static Generation\n\n```tsx\n// app/blog/[slug]/page.tsx\nexport async function generateStaticParams() {\n  const posts = await getPosts()\n  return posts.map((post) => ({ slug: post.slug }))\n}\n```\n\n### After Response\n\n```tsx\nimport { after } from 'next/server'\n\nexport async function POST(request: Request) {\n  const data = await processRequest(request)\n\n  after(async () => {\n    await logAnalytics(data)\n  })\n\n  return Response.json({ success: true })\n}\n```\n\n<!-- File: .agents/skills/next-best-practices/hydration-error.md (Tokens: ~435 | Category: anthropic-skill) -->\n# Hydration Errors\n\nDiagnose and fix React hydration mismatch errors.\n\n## Error Signs\n\n- \"Hydration failed because the initial UI does not match\"\n- \"Text content does not match server-rendered HTML\"\n\n## Debugging\n\nIn development, click the hydration error to see the server/client diff.\n\n## Common Causes and Fixes\n\n### Browser-only APIs\n\n```tsx\n// Bad: Causes mismatch - window doesn't exist on server\n;<div>{window.innerWidth}</div>\n\n// Good: Use client component with mounted check\n;('use client')\nimport { useState, useEffect } from 'react'\n\nexport function ClientOnly({ children }: { children: React.ReactNode }) {\n  const [mounted, setMounted] = useState(false)\n  useEffect(() => setMounted(true), [])\n  return mounted ? children : null\n}\n```\n\n### Date/Time Rendering\n\nServer and client may be in different timezones:\n\n```tsx\n// Bad: Causes mismatch\n;<span>{new Date().toLocaleString()}</span>\n\n// Good: Render on client only\n;('use client')\nconst [time, setTime] = useState<string>()\nuseEffect(() => setTime(new Date().toLocaleString()), [])\n```\n\n### Random Values or IDs\n\n```tsx\n// Bad: Random values differ between server and client\n<div id={Math.random().toString()}>\n\n// Good: Use useId hook\nimport { useId } from 'react'\n\nfunction Input() {\n  const id = useId()\n  return <input id={id} />\n}\n```\n\n### Invalid HTML Nesting\n\n```tsx\n// Bad: Invalid - div inside p\n<p><div>Content</div></p>\n\n// Bad: Invalid - p inside p\n<p><p>Nested</p></p>\n\n// Good: Valid nesting\n<div><p>Content</p></div>\n```\n\n### Third-party Scripts\n\nScripts that modify DOM during hydration.\n\n```tsx\n// Good: Use next/script with afterInteractive\nimport Script from 'next/script'\n\nexport default function Page() {\n  return <Script src=\"https://example.com/script.js\" strategy=\"afterInteractive\" />\n}\n```\n\n<!-- File: .agents/skills/next-best-practices/image.md (Tokens: ~1029 | Category: anthropic-skill) -->\n# Image Optimization\n\nUse `next/image` for automatic image optimization.\n\n## Always Use next/image\n\n```tsx\n// Bad: Avoid native img\n;<img src=\"/hero.png\" alt=\"Hero\" />\n\n// Good: Use next/image\nimport Image from 'next/image'\n;<Image src=\"/hero.png\" alt=\"Hero\" width={800} height={400} />\n```\n\n## Required Props\n\nImages need explicit dimensions to prevent layout shift:\n\n```tsx\n// Local images - dimensions inferred automatically\nimport heroImage from './hero.png'\n<Image src={heroImage} alt=\"Hero\" />\n\n// Remote images - must specify width/height\n<Image src=\"https://example.com/image.jpg\" alt=\"Hero\" width={800} height={400} />\n\n// Or use fill for parent-relative sizing\n<div style={{ position: 'relative', width: '100%', height: 400 }}>\n  <Image src=\"/hero.png\" alt=\"Hero\" fill style={{ objectFit: 'cover' }} />\n</div>\n```\n\n## Remote Images Configuration\n\nRemote domains must be configured in `next.config.js`:\n\n```js\n// next.config.js\nmodule.exports = {\n  images: {\n    remotePatterns: [\n      {\n        protocol: 'https',\n        hostname: 'example.com',\n        pathname: '/images/**',\n      },\n      {\n        protocol: 'https',\n        hostname: '*.cdn.com', // Wildcard subdomain\n      },\n    ],\n  },\n}\n```\n\n## Responsive Images\n\nUse `sizes` to tell the browser which size to download:\n\n```tsx\n// Full-width hero\n<Image\n  src=\"/hero.png\"\n  alt=\"Hero\"\n  fill\n  sizes=\"100vw\"\n/>\n\n// Responsive grid (3 columns on desktop, 1 on mobile)\n<Image\n  src=\"/card.png\"\n  alt=\"Card\"\n  fill\n  sizes=\"(max-width: 768px) 100vw, 33vw\"\n/>\n\n// Fixed sidebar image\n<Image\n  src=\"/avatar.png\"\n  alt=\"Avatar\"\n  width={200}\n  height={200}\n  sizes=\"200px\"\n/>\n```\n\n## Blur Placeholder\n\nPrevent layout shift with placeholders:\n\n```tsx\n// Local images - automatic blur hash\nimport heroImage from './hero.png'\n<Image src={heroImage} alt=\"Hero\" placeholder=\"blur\" />\n\n// Remote images - provide blurDataURL\n<Image\n  src=\"https://example.com/image.jpg\"\n  alt=\"Hero\"\n  width={800}\n  height={400}\n  placeholder=\"blur\"\n  blurDataURL=\"data:image/jpeg;base64,/9j/4AAQSkZJRg...\"\n/>\n\n// Or use color placeholder\n<Image\n  src=\"https://example.com/image.jpg\"\n  alt=\"Hero\"\n  width={800}\n  height={400}\n  placeholder=\"empty\"\n  style={{ backgroundColor: '#e0e0e0' }}\n/>\n```\n\n## Priority Loading\n\nUse `priority` for above-the-fold images (LCP):\n\n```tsx\n// Hero image - loads immediately\n<Image src=\"/hero.png\" alt=\"Hero\" fill priority />\n\n// Below-fold images - lazy loaded by default (no priority needed)\n<Image src=\"/card.png\" alt=\"Card\" width={400} height={300} />\n```\n\n## Common Mistakes\n\n```tsx\n// Bad: Missing sizes with fill - downloads largest image\n<Image src=\"/hero.png\" alt=\"Hero\" fill />\n\n// Good: Add sizes for proper responsive behavior\n<Image src=\"/hero.png\" alt=\"Hero\" fill sizes=\"100vw\" />\n\n// Bad: Using width/height for aspect ratio only\n<Image src=\"/hero.png\" alt=\"Hero\" width={16} height={9} />\n\n// Good: Use actual display dimensions or fill with sizes\n<Image src=\"/hero.png\" alt=\"Hero\" fill sizes=\"100vw\" style={{ objectFit: 'cover' }} />\n\n// Bad: Remote image without config\n<Image src=\"https://untrusted.com/image.jpg\" alt=\"Image\" width={400} height={300} />\n// Error: Invalid src prop, hostname not configured\n\n// Good: Add hostname to next.config.js remotePatterns\n```\n\n## Static Export\n\nWhen using `output: 'export'`, use `unoptimized` or custom loader:\n\n```tsx\n// Option 1: Disable optimization\n;<Image src=\"/hero.png\" alt=\"Hero\" width={800} height={400} unoptimized />\n\n// Option 2: Global config\n// next.config.js\nmodule.exports = {\n  output: 'export',\n  images: { unoptimized: true },\n}\n\n// Option 3: Custom loader (Cloudinary, Imgix, etc.)\nconst cloudinaryLoader = ({ src, width, quality }) => {\n  return `https://res.cloudinary.com/demo/image/upload/w_${width},q_${quality || 75}/${src}`\n}\n\n;<Image loader={cloudinaryLoader} src=\"sample.jpg\" alt=\"Sample\" width={800} height={400} />\n```\n\n<!-- File: .agents/skills/next-best-practices/metadata.md (Tokens: ~1812 | Category: anthropic-skill) -->\n# Metadata\n\nAdd SEO metadata to Next.js pages using the Metadata API.\n\n## Important: Server Components Only\n\nThe `metadata` object and `generateMetadata` function are **only supported in Server Components**. They cannot be used in Client Components.\n\nIf the target page has `'use client'`:\n\n1. Remove `'use client'` if possible, move client logic to child components\n2. Or extract metadata to a parent Server Component layout\n3. Or split the file: Server Component with metadata imports Client Components\n\n## Static Metadata\n\n```tsx\nimport type { Metadata } from 'next'\n\nexport const metadata: Metadata = {\n  title: 'Page Title',\n  description: 'Page description for search engines',\n}\n```\n\n## Dynamic Metadata\n\n```tsx\nimport type { Metadata } from 'next'\n\ntype Props = { params: Promise<{ slug: string }> }\n\nexport async function generateMetadata({ params }: Props): Promise<Metadata> {\n  const { slug } = await params\n  const post = await getPost(slug)\n  return { title: post.title, description: post.description }\n}\n```\n\n## Avoid Duplicate Fetches\n\nUse React `cache()` when the same data is needed for both metadata and page:\n\n```tsx\nimport { cache } from 'react'\n\nexport const getPost = cache(async (slug: string) => {\n  return await db.posts.findFirst({ where: { slug } })\n})\n```\n\n## Viewport\n\nSeparate from metadata for streaming support:\n\n```tsx\nimport type { Viewport } from 'next'\n\nexport const viewport: Viewport = {\n  width: 'device-width',\n  initialScale: 1,\n  themeColor: '#000000',\n}\n\n// Or dynamic\nexport function generateViewport({ params }): Viewport {\n  return { themeColor: getThemeColor(params) }\n}\n```\n\n## Title Templates\n\nIn root layout for consistent naming:\n\n```tsx\nexport const metadata: Metadata = {\n  title: { default: 'Site Name', template: '%s | Site Name' },\n}\n```\n\n## Metadata File Conventions\n\nReference: https://nextjs.org/docs/app/getting-started/project-structure#metadata-file-conventions\n\nPlace these files in `app/` directory (or route segments):\n\n| File                            | Purpose                                       |\n| ------------------------------- | --------------------------------------------- |\n| `favicon.ico`                   | Favicon                                       |\n| `icon.png` / `icon.svg`         | App icon                                      |\n| `apple-icon.png`                | Apple app icon                                |\n| `opengraph-image.png`           | OG image                                      |\n| `twitter-image.png`             | Twitter card image                            |\n| `sitemap.ts` / `sitemap.xml`    | Sitemap (use `generateSitemaps` for multiple) |\n| `robots.ts` / `robots.txt`      | Robots directives                             |\n| `manifest.ts` / `manifest.json` | Web app manifest                              |\n\n## SEO Best Practice: Static Files Are Often Enough\n\nFor most sites, **static metadata files provide excellent SEO coverage**:\n\n```\napp/\n├── favicon.ico\n├── opengraph-image.png     # Works for both OG and Twitter\n├── sitemap.ts\n├── robots.ts\n└── layout.tsx              # With title/description metadata\n```\n\n**Tips:**\n\n- A single `opengraph-image.png` covers both Open Graph and Twitter (Twitter falls back to OG)\n- Static `title` and `description` in layout metadata is sufficient for most pages\n- Only use dynamic `generateMetadata` when content varies per page\n\n---\n\n# OG Image Generation\n\nGenerate dynamic Open Graph images using `next/og`.\n\n## Important Rules\n\n1. **Use `next/og`** - not `@vercel/og` (it's built into Next.js)\n2. **No searchParams** - OG images can't access search params, use route params instead\n3. **Avoid Edge runtime** - Use default Node.js runtime\n\n```tsx\n// Good\nimport { ImageResponse } from 'next/og'\n\n// Bad\n// import { ImageResponse } from '@vercel/og'\n// export const runtime = 'edge'\n```\n\n## Basic OG Image\n\n```tsx\n// app/opengraph-image.tsx\nimport { ImageResponse } from 'next/og'\n\nexport const alt = 'Site Name'\nexport const size = { width: 1200, height: 630 }\nexport const contentType = 'image/png'\n\nexport default function Image() {\n  return new ImageResponse(\n    (\n      <div\n        style={{\n          fontSize: 128,\n          background: 'white',\n          width: '100%',\n          height: '100%',\n          display: 'flex',\n          alignItems: 'center',\n          justifyContent: 'center',\n        }}\n      >\n        Hello World\n      </div>\n    ),\n    { ...size },\n  )\n}\n```\n\n## Dynamic OG Image\n\n```tsx\n// app/blog/[slug]/opengraph-image.tsx\nimport { ImageResponse } from 'next/og'\n\nexport const alt = 'Blog Post'\nexport const size = { width: 1200, height: 630 }\nexport const contentType = 'image/png'\n\ntype Props = { params: Promise<{ slug: string }> }\n\nexport default async function Image({ params }: Props) {\n  const { slug } = await params\n  const post = await getPost(slug)\n\n  return new ImageResponse(\n    (\n      <div\n        style={{\n          fontSize: 48,\n          background: 'linear-gradient(to bottom, #1a1a1a, #333)',\n          color: 'white',\n          width: '100%',\n          height: '100%',\n          display: 'flex',\n          flexDirection: 'column',\n          alignItems: 'center',\n          justifyContent: 'center',\n          padding: 48,\n        }}\n      >\n        <div style={{ fontSize: 64, fontWeight: 'bold' }}>{post.title}</div>\n        <div style={{ marginTop: 24, opacity: 0.8 }}>{post.description}</div>\n      </div>\n    ),\n    { ...size },\n  )\n}\n```\n\n## Custom Fonts\n\n```tsx\nimport { ImageResponse } from 'next/og'\nimport { join } from 'path'\nimport { readFile } from 'fs/promises'\n\nexport default async function Image() {\n  const fontPath = join(process.cwd(), 'assets/fonts/Inter-Bold.ttf')\n  const fontData = await readFile(fontPath)\n\n  return new ImageResponse(<div style={{ fontFamily: 'Inter', fontSize: 64 }}>Custom Font Text</div>, {\n    width: 1200,\n    height: 630,\n    fonts: [{ name: 'Inter', data: fontData, style: 'normal' }],\n  })\n}\n```\n\n## File Naming\n\n- `opengraph-image.tsx` - Open Graph (Facebook, LinkedIn)\n- `twitter-image.tsx` - Twitter/X cards (optional, falls back to OG)\n\n## Styling Notes\n\nImageResponse uses Flexbox layout:\n\n- Use `display: 'flex'`\n- No CSS Grid support\n- Styles must be inline objects\n\n## Multiple OG Images\n\nUse `generateImageMetadata` for multiple images per route:\n\n```tsx\n// app/blog/[slug]/opengraph-image.tsx\nimport { ImageResponse } from 'next/og'\n\nexport async function generateImageMetadata({ params }) {\n  const images = await getPostImages(params.slug)\n  return images.map((img, idx) => ({\n    id: idx,\n    alt: img.alt,\n    size: { width: 1200, height: 630 },\n    contentType: 'image/png',\n  }))\n}\n\nexport default async function Image({ params, id }) {\n  const images = await getPostImages(params.slug)\n  const image = images[id]\n  return new ImageResponse(/* ... */)\n}\n```\n\n## Multiple Sitemaps\n\nUse `generateSitemaps` for large sites:\n\n```tsx\n// app/sitemap.ts\nimport type { MetadataRoute } from 'next'\n\nexport async function generateSitemaps() {\n  // Return array of sitemap IDs\n  return [{ id: 0 }, { id: 1 }, { id: 2 }]\n}\n\nexport default async function sitemap({ id }: { id: number }): Promise<MetadataRoute.Sitemap> {\n  const start = id * 50000\n  const end = start + 50000\n  const products = await getProducts(start, end)\n\n  return products.map((product) => ({\n    url: `https://example.com/product/${product.id}`,\n    lastModified: product.updatedAt,\n  }))\n}\n```\n\nGenerates `/sitemap/0.xml`, `/sitemap/1.xml`, etc.\n\n<!-- File: .agents/skills/next-best-practices/parallel-routes.md (Tokens: ~1834 | Category: anthropic-skill) -->\n# Parallel & Intercepting Routes\n\nParallel routes render multiple pages in the same layout. Intercepting routes show a different UI when navigating from within your app vs direct URL access. Together they enable modal patterns.\n\n## File Structure\n\n```\napp/\n├── @modal/                    # Parallel route slot\n│   ├── default.tsx            # Required! Returns null\n│   ├── (.)photos/             # Intercepts /photos/*\n│   │   └── [id]/\n│   │       └── page.tsx       # Modal content\n│   └── [...]catchall/         # Optional: catch unmatched\n│       └── page.tsx\n├── photos/\n│   └── [id]/\n│       └── page.tsx           # Full page (direct access)\n├── layout.tsx                 # Renders both children and @modal\n└── page.tsx\n```\n\n## Step 1: Root Layout with Slot\n\n```tsx\n// app/layout.tsx\nexport default function RootLayout({ children, modal }: { children: React.ReactNode; modal: React.ReactNode }) {\n  return (\n    <html>\n      <body>\n        {children}\n        {modal}\n      </body>\n    </html>\n  )\n}\n```\n\n## Step 2: Default File (Critical!)\n\n**Every parallel route slot MUST have a `default.tsx`** to prevent 404s on hard navigation.\n\n```tsx\n// app/@modal/default.tsx\nexport default function Default() {\n  return null\n}\n```\n\nWithout this file, refreshing any page will 404 because Next.js can't determine what to render in the `@modal` slot.\n\n## Step 3: Intercepting Route (Modal)\n\nThe `(.)` prefix intercepts routes at the same level.\n\n```tsx\n// app/@modal/(.)photos/[id]/page.tsx\nimport { Modal } from '@/components/modal'\n\nexport default async function PhotoModal({ params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n  const photo = await getPhoto(id)\n\n  return (\n    <Modal>\n      <img src={photo.url} alt={photo.title} />\n    </Modal>\n  )\n}\n```\n\n## Step 4: Full Page (Direct Access)\n\n```tsx\n// app/photos/[id]/page.tsx\nexport default async function PhotoPage({ params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n  const photo = await getPhoto(id)\n\n  return (\n    <div className=\"full-page\">\n      <img src={photo.url} alt={photo.title} />\n      <h1>{photo.title}</h1>\n    </div>\n  )\n}\n```\n\n## Step 5: Modal Component with Correct Closing\n\n**Critical: Use `router.back()` to close modals, NOT `router.push()` or `<Link>`.**\n\n```tsx\n// components/modal.tsx\n'use client'\n\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useRef } from 'react'\n\nexport function Modal({ children }: { children: React.ReactNode }) {\n  const router = useRouter()\n  const overlayRef = useRef<HTMLDivElement>(null)\n\n  // Close on escape key\n  useEffect(() => {\n    function onKeyDown(e: KeyboardEvent) {\n      if (e.key === 'Escape') {\n        router.back() // Correct\n      }\n    }\n    document.addEventListener('keydown', onKeyDown)\n    return () => document.removeEventListener('keydown', onKeyDown)\n  }, [router])\n\n  // Close on overlay click\n  const handleOverlayClick = useCallback(\n    (e: React.MouseEvent) => {\n      if (e.target === overlayRef.current) {\n        router.back() // Correct\n      }\n    },\n    [router],\n  )\n\n  return (\n    <div\n      ref={overlayRef}\n      onClick={handleOverlayClick}\n      className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\"\n    >\n      <div className=\"mx-4 w-full max-w-2xl rounded-lg bg-white p-6\">\n        <button\n          onClick={() => router.back()} // Correct!\n          className=\"absolute top-4 right-4\"\n        >\n          Close\n        </button>\n        {children}\n      </div>\n    </div>\n  )\n}\n```\n\n### Why NOT `router.push('/')` or `<Link href=\"/\">`?\n\nUsing `push` or `Link` to \"close\" a modal:\n\n1. Adds a new history entry (back button shows modal again)\n2. Doesn't properly clear the intercepted route\n3. Can cause the modal to flash or persist unexpectedly\n\n`router.back()` correctly:\n\n1. Removes the intercepted route from history\n2. Returns to the previous page\n3. Properly unmounts the modal\n\n## Route Matcher Reference\n\nMatchers match **route segments**, not filesystem paths:\n\n| Matcher    | Matches       | Example                                                               |\n| ---------- | ------------- | --------------------------------------------------------------------- |\n| `(.)`      | Same level    | `@modal/(.)photos` intercepts `/photos`                               |\n| `(..)`     | One level up  | `@modal/(..)settings` from `/dashboard/@modal` intercepts `/settings` |\n| `(..)(..)` | Two levels up | Rarely used                                                           |\n| `(...)`    | From root     | `@modal/(...)photos` intercepts `/photos` from anywhere               |\n\n**Common mistake**: Thinking `(..)` means \"parent folder\" - it means \"parent route segment\".\n\n## Handling Hard Navigation\n\nWhen users directly visit `/photos/123` (bookmark, refresh, shared link):\n\n- The intercepting route is bypassed\n- The full `photos/[id]/page.tsx` renders\n- Modal doesn't appear (expected behavior)\n\nIf you want the modal to appear on direct access too, you need additional logic:\n\n```tsx\n// app/photos/[id]/page.tsx\nimport { Modal } from '@/components/modal'\n\nexport default async function PhotoPage({ params }) {\n  const { id } = await params\n  const photo = await getPhoto(id)\n\n  // Option: Render as modal on direct access too\n  return (\n    <Modal>\n      <img src={photo.url} alt={photo.title} />\n    </Modal>\n  )\n}\n```\n\n## Common Gotchas\n\n### 1. Missing `default.tsx` → 404 on Refresh\n\nEvery `@slot` folder needs a `default.tsx` that returns `null` (or appropriate content).\n\n### 2. Modal Persists After Navigation\n\nYou're using `router.push()` instead of `router.back()`.\n\n### 3. Nested Parallel Routes Need Defaults Too\n\nIf you have `@modal` inside a route group, each level needs its own `default.tsx`:\n\n```\napp/\n├── (marketing)/\n│   ├── @modal/\n│   │   └── default.tsx     # Needed!\n│   └── layout.tsx\n└── layout.tsx\n```\n\n### 4. Intercepted Route Shows Wrong Content\n\nCheck your matcher:\n\n- `(.)photos` intercepts `/photos` from the same route level\n- If your `@modal` is in `app/dashboard/@modal`, use `(.)photos` to intercept `/dashboard/photos`, not `/photos`\n\n### 5. TypeScript Errors with `params`\n\nIn Next.js 15+, `params` is a Promise:\n\n```tsx\n// Correct\nexport default async function Page({ params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n}\n```\n\n## Complete Example: Photo Gallery Modal\n\n```\napp/\n├── @modal/\n│   ├── default.tsx\n│   └── (.)photos/\n│       └── [id]/\n│           └── page.tsx\n├── photos/\n│   ├── page.tsx           # Gallery grid\n│   └── [id]/\n│       └── page.tsx       # Full photo page\n├── layout.tsx\n└── page.tsx\n```\n\nLinks in the gallery:\n\n```tsx\n// app/photos/page.tsx\nimport Link from 'next/link'\n\nexport default async function Gallery() {\n  const photos = await getPhotos()\n\n  return (\n    <div className=\"grid grid-cols-3 gap-4\">\n      {photos.map((photo) => (\n        <Link key={photo.id} href={`/photos/${photo.id}`}>\n          <img src={photo.thumbnail} alt={photo.title} />\n        </Link>\n      ))}\n    </div>\n  )\n}\n```\n\nClicking a photo → Modal opens (intercepted)\nDirect URL → Full page renders\nRefresh while modal open → Full page renders\n\n<!-- File: .agents/skills/next-best-practices/route-handlers.md (Tokens: ~836 | Category: anthropic-skill) -->\n# Route Handlers\n\nCreate API endpoints with `route.ts` files.\n\n## Basic Usage\n\n```tsx\n// app/api/users/route.ts\nexport async function GET() {\n  const users = await getUsers()\n  return Response.json(users)\n}\n\nexport async function POST(request: Request) {\n  const body = await request.json()\n  const user = await createUser(body)\n  return Response.json(user, { status: 201 })\n}\n```\n\n## Supported Methods\n\n`GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`\n\n## GET Handler Conflicts with page.tsx\n\n**A `route.ts` and `page.tsx` cannot coexist in the same folder.**\n\n```\napp/\n├── api/\n│   └── users/\n│       └── route.ts    # /api/users\n└── users/\n    ├── page.tsx        # /users (page)\n    └── route.ts        # Warning: Conflicts with page.tsx!\n```\n\nIf you need both a page and an API at the same path, use different paths:\n\n```\napp/\n├── users/\n│   └── page.tsx        # /users (page)\n└── api/\n    └── users/\n        └── route.ts    # /api/users (API)\n```\n\n## Environment Behavior\n\nRoute handlers run in a **Server Component-like environment**:\n\n- Yes: Can use `async/await`\n- Yes: Can access `cookies()`, `headers()`\n- Yes: Can use Node.js APIs\n- No: Cannot use React hooks\n- No: Cannot use React DOM APIs\n- No: Cannot use browser APIs\n\n```tsx\n// Bad: This won't work - no React DOM in route handlers\nimport { renderToString } from 'react-dom/server'\n\nexport async function GET() {\n  const html = renderToString(<Component />) // Error!\n  return new Response(html)\n}\n```\n\n## Dynamic Route Handlers\n\n```tsx\n// app/api/users/[id]/route.ts\nexport async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n  const user = await getUser(id)\n\n  if (!user) {\n    return Response.json({ error: 'Not found' }, { status: 404 })\n  }\n\n  return Response.json(user)\n}\n```\n\n## Request Helpers\n\n```tsx\nexport async function GET(request: Request) {\n  // URL and search params\n  const { searchParams } = new URL(request.url)\n  const query = searchParams.get('q')\n\n  // Headers\n  const authHeader = request.headers.get('authorization')\n\n  // Cookies (Next.js helper)\n  const cookieStore = await cookies()\n  const token = cookieStore.get('token')\n\n  return Response.json({ query, token })\n}\n```\n\n## Response Helpers\n\n```tsx\n// JSON response\nreturn Response.json({ data })\n\n// With status\nreturn Response.json({ error: 'Not found' }, { status: 404 })\n\n// With headers\nreturn Response.json(data, {\n  headers: {\n    'Cache-Control': 'max-age=3600',\n  },\n})\n\n// Redirect\nreturn Response.redirect(new URL('/login', request.url))\n\n// Stream\nreturn new Response(stream, {\n  headers: { 'Content-Type': 'text/event-stream' },\n})\n```\n\n## When to Use Route Handlers vs Server Actions\n\n| Use Case                 | Route Handlers | Server Actions |\n| ------------------------ | -------------- | -------------- |\n| Form submissions         | No             | Yes            |\n| Data mutations from UI   | No             | Yes            |\n| Third-party webhooks     | Yes            | No             |\n| External API consumption | Yes            | No             |\n| Public REST API          | Yes            | No             |\n| File uploads             | Both work      | Both work      |\n\n**Prefer Server Actions** for mutations triggered from your UI.\n**Use Route Handlers** for external integrations and public APIs.\n\n<!-- ... +68 more modular skills available via /api/skills/JimmyLv/BibiGPT-v1?path={path} -->\n</yakaai_skills>"}