From bf58891edf6b2a9fc6e93433c483abccb068617f Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Tue, 27 Jan 2026 05:06:33 -0800 Subject: [PATCH] chore: sync workspace changes --- .agents/skills/agent-browser/SKILL.md | 356 ++ .../references/authentication.md | 188 ++ .../agent-browser/references/proxy-support.md | 175 + .../references/session-management.md | 181 + .../agent-browser/references/snapshot-refs.md | 186 ++ .../references/video-recording.md | 162 + .../templates/authenticated-session.sh | 91 + .../templates/capture-workflow.sh | 68 + .../templates/form-automation.sh | 64 + .agents/skills/frontend-design/LICENSE.txt | 177 - .agents/skills/frontend-design/SKILL.md | 42 - .../vercel-react-best-practices/AGENTS.md | 2934 ----------------- .../vercel-react-best-practices/SKILL.md | 136 - .../rules/advanced-event-handler-refs.md | 55 - .../rules/advanced-init-once.md | 42 - .../rules/advanced-use-latest.md | 39 - .../rules/async-api-routes.md | 38 - .../rules/async-defer-await.md | 80 - .../rules/async-dependencies.md | 51 - .../rules/async-parallel.md | 28 - .../rules/async-suspense-boundaries.md | 99 - .../rules/bundle-barrel-imports.md | 59 - .../rules/bundle-conditional.md | 31 - .../rules/bundle-defer-third-party.md | 49 - .../rules/bundle-dynamic-imports.md | 35 - .../rules/bundle-preload.md | 50 - .../rules/client-event-listeners.md | 74 - .../rules/client-localstorage-schema.md | 71 - .../rules/client-passive-event-listeners.md | 48 - .../rules/client-swr-dedup.md | 56 - .../rules/js-batch-dom-css.md | 107 - .../rules/js-cache-function-results.md | 80 - .../rules/js-cache-property-access.md | 28 - .../rules/js-cache-storage.md | 70 - .../rules/js-combine-iterations.md | 32 - .../rules/js-early-exit.md | 50 - .../rules/js-hoist-regexp.md | 45 - .../rules/js-index-maps.md | 37 - .../rules/js-length-check-first.md | 49 - .../rules/js-min-max-loop.md | 82 - .../rules/js-set-map-lookups.md | 24 - .../rules/js-tosorted-immutable.md | 57 - .../rules/rendering-activity.md | 26 - .../rules/rendering-animate-svg-wrapper.md | 47 - .../rules/rendering-conditional-render.md | 40 - .../rules/rendering-content-visibility.md | 38 - .../rules/rendering-hoist-jsx.md | 46 - .../rules/rendering-hydration-no-flicker.md | 82 - .../rendering-hydration-suppress-warning.md | 30 - .../rules/rendering-svg-precision.md | 28 - .../rules/rendering-usetransition-loading.md | 75 - .../rules/rerender-defer-reads.md | 39 - .../rules/rerender-dependencies.md | 45 - .../rules/rerender-derived-state-no-effect.md | 40 - .../rules/rerender-derived-state.md | 29 - .../rules/rerender-functional-setstate.md | 74 - .../rules/rerender-lazy-state-init.md | 58 - .../rules/rerender-memo-with-default-value.md | 38 - .../rules/rerender-memo.md | 44 - .../rules/rerender-move-effect-to-event.md | 45 - .../rerender-simple-expression-in-memo.md | 35 - .../rules/rerender-transitions.md | 40 - .../rerender-use-ref-transient-values.md | 73 - .../rules/server-after-nonblocking.md | 73 - .../rules/server-auth-actions.md | 96 - .../rules/server-cache-lru.md | 41 - .../rules/server-cache-react.md | 76 - .../rules/server-dedup-props.md | 65 - .../rules/server-parallel-fetching.md | 83 - .../rules/server-serialization.md | 38 - .claude/skills/agent-browser | 1 + .codex/skills/agent-browser | 1 + CLAUDE.md | 17 +- README.md | 150 +- ROADMAP.md | 77 - docs/agent-compatibility.mdx | 1 + docs/ai/llms-txt.mdx | 15 + docs/ai/skill.mdx | 21 + docs/architecture.mdx | 24 +- docs/building-chat-ui.mdx | 28 +- docs/cli.mdx | 25 +- docs/{deployments => deploy}/daytona.mdx | 0 docs/{deployments => deploy}/docker.mdx | 0 docs/{deployments => deploy}/e2b.mdx | 0 .../vercel-sandboxes.mdx | 0 docs/deployments/cloudflare-sandboxes.mdx | 21 - docs/docs.json | 150 +- docs/frontend.mdx | 6 +- docs/index.mdx | 2 +- docs/openapi.json | 48 +- docs/persisting-chat-logs.mdx | 21 + docs/quickstart.mdx | 16 +- docs/sdks/typescript.mdx | 44 +- docs/telemetry.mdx | 30 + docs/typescript-sdk.mdx | 118 - frontend/packages/inspector/index.html | 146 +- frontend/packages/inspector/src/App.tsx | 977 +----- .../src/components/ConnectScreen.tsx | 101 + .../src/components/SessionSidebar.tsx | 55 + .../components/agents/CapabilityBadges.tsx | 57 + .../src/components/chat/ChatInput.tsx | 37 + .../src/components/chat/ChatMessages.tsx | 75 + .../src/components/chat/ChatPanel.tsx | 164 + .../src/components/chat/ChatSetup.tsx | 133 + .../src/components/chat/messageUtils.ts | 18 + .../src/components/chat/renderContentPart.tsx | 93 + .../inspector/src/components/chat/types.ts | 14 + .../src/components/debug/AgentsTab.tsx | 71 + .../src/components/debug/ApprovalsTab.tsx | 105 + .../src/components/debug/DebugPanel.tsx | 89 + .../src/components/debug/EventsTab.tsx | 91 + .../src/components/debug/RequestLogTab.tsx | 52 + .../src/components/debug/eventUtils.ts | 63 + .../packages/inspector/src/types/agents.ts | 33 + .../inspector/src/types/requestLog.ts | 10 + .../packages/inspector/src/utils/format.ts | 18 + frontend/packages/inspector/src/utils/http.ts | 15 + package.json | 2 +- research.md | 22 - research/detect-sandbox.md | 599 ++++ sdks/cli/package.json | 3 +- sdks/typescript/src/client.ts | 66 +- sdks/typescript/src/generated/openapi.ts | 11 + sdks/typescript/src/index.ts | 13 +- sdks/typescript/src/spawn.ts | 16 +- sdks/typescript/tests/client.test.ts | 72 +- sdks/typescript/tests/integration.test.ts | 47 +- sdks/typescript/tests/sse-parser.test.ts | 18 +- .../packages/agent-management/src/agents.rs | 32 + .../packages/agent-management/src/testing.rs | 1 + server/packages/sandbox-agent/src/lib.rs | 3 +- server/packages/sandbox-agent/src/main.rs | 30 +- server/packages/sandbox-agent/src/router.rs | 1616 +++++---- .../packages/sandbox-agent/src/telemetry.rs | 373 +++ .../sandbox-agent/tests/agent_agnostic.rs | 7 +- .../sandbox-agent/tests/http_sse_snapshots.rs | 4 +- .../sandbox-agent/tests/inspector_ui.rs | 4 +- spec.md | 494 --- todo.md | 4 +- 139 files changed, 5454 insertions(+), 8986 deletions(-) create mode 100644 .agents/skills/agent-browser/SKILL.md create mode 100644 .agents/skills/agent-browser/references/authentication.md create mode 100644 .agents/skills/agent-browser/references/proxy-support.md create mode 100644 .agents/skills/agent-browser/references/session-management.md create mode 100644 .agents/skills/agent-browser/references/snapshot-refs.md create mode 100644 .agents/skills/agent-browser/references/video-recording.md create mode 100755 .agents/skills/agent-browser/templates/authenticated-session.sh create mode 100755 .agents/skills/agent-browser/templates/capture-workflow.sh create mode 100755 .agents/skills/agent-browser/templates/form-automation.sh delete mode 100644 .agents/skills/frontend-design/LICENSE.txt delete mode 100644 .agents/skills/frontend-design/SKILL.md delete mode 100644 .agents/skills/vercel-react-best-practices/AGENTS.md delete mode 100644 .agents/skills/vercel-react-best-practices/SKILL.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/advanced-init-once.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/advanced-use-latest.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/async-api-routes.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/async-defer-await.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/async-dependencies.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/async-parallel.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/bundle-conditional.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/bundle-preload.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/client-event-listeners.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/js-cache-storage.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/js-early-exit.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/js-index-maps.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/js-length-check-first.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-activity.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-conditional-render.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-content-visibility.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-svg-precision.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-defer-reads.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-dependencies.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-derived-state.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-memo.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-transitions.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/server-after-nonblocking.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/server-auth-actions.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/server-cache-lru.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/server-cache-react.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/server-dedup-props.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/server-parallel-fetching.md delete mode 100644 .agents/skills/vercel-react-best-practices/rules/server-serialization.md create mode 120000 .claude/skills/agent-browser create mode 120000 .codex/skills/agent-browser delete mode 100644 ROADMAP.md create mode 100644 docs/ai/llms-txt.mdx create mode 100644 docs/ai/skill.mdx rename docs/{deployments => deploy}/daytona.mdx (100%) rename docs/{deployments => deploy}/docker.mdx (100%) rename docs/{deployments => deploy}/e2b.mdx (100%) rename docs/{deployments => deploy}/vercel-sandboxes.mdx (100%) delete mode 100644 docs/deployments/cloudflare-sandboxes.mdx create mode 100644 docs/persisting-chat-logs.mdx create mode 100644 docs/telemetry.mdx delete mode 100644 docs/typescript-sdk.mdx create mode 100644 frontend/packages/inspector/src/components/ConnectScreen.tsx create mode 100644 frontend/packages/inspector/src/components/SessionSidebar.tsx create mode 100644 frontend/packages/inspector/src/components/agents/CapabilityBadges.tsx create mode 100644 frontend/packages/inspector/src/components/chat/ChatInput.tsx create mode 100644 frontend/packages/inspector/src/components/chat/ChatMessages.tsx create mode 100644 frontend/packages/inspector/src/components/chat/ChatPanel.tsx create mode 100644 frontend/packages/inspector/src/components/chat/ChatSetup.tsx create mode 100644 frontend/packages/inspector/src/components/chat/messageUtils.ts create mode 100644 frontend/packages/inspector/src/components/chat/renderContentPart.tsx create mode 100644 frontend/packages/inspector/src/components/chat/types.ts create mode 100644 frontend/packages/inspector/src/components/debug/AgentsTab.tsx create mode 100644 frontend/packages/inspector/src/components/debug/ApprovalsTab.tsx create mode 100644 frontend/packages/inspector/src/components/debug/DebugPanel.tsx create mode 100644 frontend/packages/inspector/src/components/debug/EventsTab.tsx create mode 100644 frontend/packages/inspector/src/components/debug/RequestLogTab.tsx create mode 100644 frontend/packages/inspector/src/components/debug/eventUtils.ts create mode 100644 frontend/packages/inspector/src/types/agents.ts create mode 100644 frontend/packages/inspector/src/types/requestLog.ts create mode 100644 frontend/packages/inspector/src/utils/format.ts create mode 100644 frontend/packages/inspector/src/utils/http.ts delete mode 100644 research.md create mode 100644 research/detect-sandbox.md create mode 100644 server/packages/sandbox-agent/src/telemetry.rs delete mode 100644 spec.md diff --git a/.agents/skills/agent-browser/SKILL.md b/.agents/skills/agent-browser/SKILL.md new file mode 100644 index 0000000..ab3ea3c --- /dev/null +++ b/.agents/skills/agent-browser/SKILL.md @@ -0,0 +1,356 @@ +--- +name: agent-browser +description: Automates browser interactions for web testing, form filling, screenshots, and data extraction. Use when the user needs to navigate websites, interact with web pages, fill forms, take screenshots, test web applications, or extract information from web pages. +allowed-tools: Bash(agent-browser:*) +--- + +# Browser Automation with agent-browser + +## Quick start + +```bash +agent-browser open # Navigate to page +agent-browser snapshot -i # Get interactive elements with refs +agent-browser click @e1 # Click element by ref +agent-browser fill @e2 "text" # Fill input by ref +agent-browser close # Close browser +``` + +## Core workflow + +1. Navigate: `agent-browser open ` +2. Snapshot: `agent-browser snapshot -i` (returns elements with refs like `@e1`, `@e2`) +3. Interact using refs from the snapshot +4. Re-snapshot after navigation or significant DOM changes + +## Commands + +### Navigation + +```bash +agent-browser open # Navigate to URL (aliases: goto, navigate) + # Supports: https://, http://, file://, about:, data:// + # Auto-prepends https:// if no protocol given +agent-browser back # Go back +agent-browser forward # Go forward +agent-browser reload # Reload page +agent-browser close # Close browser (aliases: quit, exit) +agent-browser connect 9222 # Connect to browser via CDP port +``` + +### Snapshot (page analysis) + +```bash +agent-browser snapshot # Full accessibility tree +agent-browser snapshot -i # Interactive elements only (recommended) +agent-browser snapshot -c # Compact output +agent-browser snapshot -d 3 # Limit depth to 3 +agent-browser snapshot -s "#main" # Scope to CSS selector +``` + +### Interactions (use @refs from snapshot) + +```bash +agent-browser click @e1 # Click +agent-browser dblclick @e1 # Double-click +agent-browser focus @e1 # Focus element +agent-browser fill @e2 "text" # Clear and type +agent-browser type @e2 "text" # Type without clearing +agent-browser press Enter # Press key (alias: key) +agent-browser press Control+a # Key combination +agent-browser keydown Shift # Hold key down +agent-browser keyup Shift # Release key +agent-browser hover @e1 # Hover +agent-browser check @e1 # Check checkbox +agent-browser uncheck @e1 # Uncheck checkbox +agent-browser select @e1 "value" # Select dropdown option +agent-browser select @e1 "a" "b" # Select multiple options +agent-browser scroll down 500 # Scroll page (default: down 300px) +agent-browser scrollintoview @e1 # Scroll element into view (alias: scrollinto) +agent-browser drag @e1 @e2 # Drag and drop +agent-browser upload @e1 file.pdf # Upload files +``` + +### Get information + +```bash +agent-browser get text @e1 # Get element text +agent-browser get html @e1 # Get innerHTML +agent-browser get value @e1 # Get input value +agent-browser get attr @e1 href # Get attribute +agent-browser get title # Get page title +agent-browser get url # Get current URL +agent-browser get count ".item" # Count matching elements +agent-browser get box @e1 # Get bounding box +agent-browser get styles @e1 # Get computed styles (font, color, bg, etc.) +``` + +### Check state + +```bash +agent-browser is visible @e1 # Check if visible +agent-browser is enabled @e1 # Check if enabled +agent-browser is checked @e1 # Check if checked +``` + +### Screenshots & PDF + +```bash +agent-browser screenshot # Save to a temporary directory +agent-browser screenshot path.png # Save to a specific path +agent-browser screenshot --full # Full page +agent-browser pdf output.pdf # Save as PDF +``` + +### Video recording + +```bash +agent-browser record start ./demo.webm # Start recording (uses current URL + state) +agent-browser click @e1 # Perform actions +agent-browser record stop # Stop and save video +agent-browser record restart ./take2.webm # Stop current + start new recording +``` + +Recording creates a fresh context but preserves cookies/storage from your session. If no URL is provided, it +automatically returns to your current page. For smooth demos, explore first, then start recording. + +### Wait + +```bash +agent-browser wait @e1 # Wait for element +agent-browser wait 2000 # Wait milliseconds +agent-browser wait --text "Success" # Wait for text (or -t) +agent-browser wait --url "**/dashboard" # Wait for URL pattern (or -u) +agent-browser wait --load networkidle # Wait for network idle (or -l) +agent-browser wait --fn "window.ready" # Wait for JS condition (or -f) +``` + +### Mouse control + +```bash +agent-browser mouse move 100 200 # Move mouse +agent-browser mouse down left # Press button +agent-browser mouse up left # Release button +agent-browser mouse wheel 100 # Scroll wheel +``` + +### Semantic locators (alternative to refs) + +```bash +agent-browser find role button click --name "Submit" +agent-browser find text "Sign In" click +agent-browser find text "Sign In" click --exact # Exact match only +agent-browser find label "Email" fill "user@test.com" +agent-browser find placeholder "Search" type "query" +agent-browser find alt "Logo" click +agent-browser find title "Close" click +agent-browser find testid "submit-btn" click +agent-browser find first ".item" click +agent-browser find last ".item" click +agent-browser find nth 2 "a" hover +``` + +### Browser settings + +```bash +agent-browser set viewport 1920 1080 # Set viewport size +agent-browser set device "iPhone 14" # Emulate device +agent-browser set geo 37.7749 -122.4194 # Set geolocation (alias: geolocation) +agent-browser set offline on # Toggle offline mode +agent-browser set headers '{"X-Key":"v"}' # Extra HTTP headers +agent-browser set credentials user pass # HTTP basic auth (alias: auth) +agent-browser set media dark # Emulate color scheme +agent-browser set media light reduced-motion # Light mode + reduced motion +``` + +### Cookies & Storage + +```bash +agent-browser cookies # Get all cookies +agent-browser cookies set name value # Set cookie +agent-browser cookies clear # Clear cookies +agent-browser storage local # Get all localStorage +agent-browser storage local key # Get specific key +agent-browser storage local set k v # Set value +agent-browser storage local clear # Clear all +``` + +### Network + +```bash +agent-browser network route # Intercept requests +agent-browser network route --abort # Block requests +agent-browser network route --body '{}' # Mock response +agent-browser network unroute [url] # Remove routes +agent-browser network requests # View tracked requests +agent-browser network requests --filter api # Filter requests +``` + +### Tabs & Windows + +```bash +agent-browser tab # List tabs +agent-browser tab new [url] # New tab +agent-browser tab 2 # Switch to tab by index +agent-browser tab close # Close current tab +agent-browser tab close 2 # Close tab by index +agent-browser window new # New window +``` + +### Frames + +```bash +agent-browser frame "#iframe" # Switch to iframe +agent-browser frame main # Back to main frame +``` + +### Dialogs + +```bash +agent-browser dialog accept [text] # Accept dialog +agent-browser dialog dismiss # Dismiss dialog +``` + +### JavaScript + +```bash +agent-browser eval "document.title" # Run JavaScript +``` + +## Global options + +```bash +agent-browser --session ... # Isolated browser session +agent-browser --json ... # JSON output for parsing +agent-browser --headed ... # Show browser window (not headless) +agent-browser --full ... # Full page screenshot (-f) +agent-browser --cdp ... # Connect via Chrome DevTools Protocol +agent-browser -p ... # Cloud browser provider (--provider) +agent-browser --proxy ... # Use proxy server +agent-browser --headers ... # HTTP headers scoped to URL's origin +agent-browser --executable-path

# Custom browser executable +agent-browser --extension ... # Load browser extension (repeatable) +agent-browser --help # Show help (-h) +agent-browser --version # Show version (-V) +agent-browser --help # Show detailed help for a command +``` + +### Proxy support + +```bash +agent-browser --proxy http://proxy.com:8080 open example.com +agent-browser --proxy http://user:pass@proxy.com:8080 open example.com +agent-browser --proxy socks5://proxy.com:1080 open example.com +``` + +## Environment variables + +```bash +AGENT_BROWSER_SESSION="mysession" # Default session name +AGENT_BROWSER_EXECUTABLE_PATH="/path/chrome" # Custom browser path +AGENT_BROWSER_EXTENSIONS="/ext1,/ext2" # Comma-separated extension paths +AGENT_BROWSER_PROVIDER="your-cloud-browser-provider" # Cloud browser provider (select browseruse or browserbase) +AGENT_BROWSER_STREAM_PORT="9223" # WebSocket streaming port +AGENT_BROWSER_HOME="/path/to/agent-browser" # Custom install location (for daemon.js) +``` + +## Example: Form submission + +```bash +agent-browser open https://example.com/form +agent-browser snapshot -i +# Output shows: textbox "Email" [ref=e1], textbox "Password" [ref=e2], button "Submit" [ref=e3] + +agent-browser fill @e1 "user@example.com" +agent-browser fill @e2 "password123" +agent-browser click @e3 +agent-browser wait --load networkidle +agent-browser snapshot -i # Check result +``` + +## Example: Authentication with saved state + +```bash +# Login once +agent-browser open https://app.example.com/login +agent-browser snapshot -i +agent-browser fill @e1 "username" +agent-browser fill @e2 "password" +agent-browser click @e3 +agent-browser wait --url "**/dashboard" +agent-browser state save auth.json + +# Later sessions: load saved state +agent-browser state load auth.json +agent-browser open https://app.example.com/dashboard +``` + +## Sessions (parallel browsers) + +```bash +agent-browser --session test1 open site-a.com +agent-browser --session test2 open site-b.com +agent-browser session list +``` + +## JSON output (for parsing) + +Add `--json` for machine-readable output: + +```bash +agent-browser snapshot -i --json +agent-browser get text @e1 --json +``` + +## Debugging + +```bash +agent-browser --headed open example.com # Show browser window +agent-browser --cdp 9222 snapshot # Connect via CDP port +agent-browser connect 9222 # Alternative: connect command +agent-browser console # View console messages +agent-browser console --clear # Clear console +agent-browser errors # View page errors +agent-browser errors --clear # Clear errors +agent-browser highlight @e1 # Highlight element +agent-browser trace start # Start recording trace +agent-browser trace stop trace.zip # Stop and save trace +agent-browser record start ./debug.webm # Record video from current page +agent-browser record stop # Save recording +``` + +## Deep-dive documentation + +For detailed patterns and best practices, see: + +| Reference | Description | +|-----------|-------------| +| [references/snapshot-refs.md](references/snapshot-refs.md) | Ref lifecycle, invalidation rules, troubleshooting | +| [references/session-management.md](references/session-management.md) | Parallel sessions, state persistence, concurrent scraping | +| [references/authentication.md](references/authentication.md) | Login flows, OAuth, 2FA handling, state reuse | +| [references/video-recording.md](references/video-recording.md) | Recording workflows for debugging and documentation | +| [references/proxy-support.md](references/proxy-support.md) | Proxy configuration, geo-testing, rotating proxies | + +## Ready-to-use templates + +Executable workflow scripts for common patterns: + +| Template | Description | +|----------|-------------| +| [templates/form-automation.sh](templates/form-automation.sh) | Form filling with validation | +| [templates/authenticated-session.sh](templates/authenticated-session.sh) | Login once, reuse state | +| [templates/capture-workflow.sh](templates/capture-workflow.sh) | Content extraction with screenshots | + +Usage: +```bash +./templates/form-automation.sh https://example.com/form +./templates/authenticated-session.sh https://app.example.com/login +./templates/capture-workflow.sh https://example.com ./output +``` + +## HTTPS Certificate Errors + +For sites with self-signed or invalid certificates: +```bash +agent-browser open https://localhost:8443 --ignore-https-errors +``` diff --git a/.agents/skills/agent-browser/references/authentication.md b/.agents/skills/agent-browser/references/authentication.md new file mode 100644 index 0000000..5d801f6 --- /dev/null +++ b/.agents/skills/agent-browser/references/authentication.md @@ -0,0 +1,188 @@ +# Authentication Patterns + +Patterns for handling login flows, session persistence, and authenticated browsing. + +## Basic Login Flow + +```bash +# Navigate to login page +agent-browser open https://app.example.com/login +agent-browser wait --load networkidle + +# Get form elements +agent-browser snapshot -i +# Output: @e1 [input type="email"], @e2 [input type="password"], @e3 [button] "Sign In" + +# Fill credentials +agent-browser fill @e1 "user@example.com" +agent-browser fill @e2 "password123" + +# Submit +agent-browser click @e3 +agent-browser wait --load networkidle + +# Verify login succeeded +agent-browser get url # Should be dashboard, not login +``` + +## Saving Authentication State + +After logging in, save state for reuse: + +```bash +# Login first (see above) +agent-browser open https://app.example.com/login +agent-browser snapshot -i +agent-browser fill @e1 "user@example.com" +agent-browser fill @e2 "password123" +agent-browser click @e3 +agent-browser wait --url "**/dashboard" + +# Save authenticated state +agent-browser state save ./auth-state.json +``` + +## Restoring Authentication + +Skip login by loading saved state: + +```bash +# Load saved auth state +agent-browser state load ./auth-state.json + +# Navigate directly to protected page +agent-browser open https://app.example.com/dashboard + +# Verify authenticated +agent-browser snapshot -i +``` + +## OAuth / SSO Flows + +For OAuth redirects: + +```bash +# Start OAuth flow +agent-browser open https://app.example.com/auth/google + +# Handle redirects automatically +agent-browser wait --url "**/accounts.google.com**" +agent-browser snapshot -i + +# Fill Google credentials +agent-browser fill @e1 "user@gmail.com" +agent-browser click @e2 # Next button +agent-browser wait 2000 +agent-browser snapshot -i +agent-browser fill @e3 "password" +agent-browser click @e4 # Sign in + +# Wait for redirect back +agent-browser wait --url "**/app.example.com**" +agent-browser state save ./oauth-state.json +``` + +## Two-Factor Authentication + +Handle 2FA with manual intervention: + +```bash +# Login with credentials +agent-browser open https://app.example.com/login --headed # Show browser +agent-browser snapshot -i +agent-browser fill @e1 "user@example.com" +agent-browser fill @e2 "password123" +agent-browser click @e3 + +# Wait for user to complete 2FA manually +echo "Complete 2FA in the browser window..." +agent-browser wait --url "**/dashboard" --timeout 120000 + +# Save state after 2FA +agent-browser state save ./2fa-state.json +``` + +## HTTP Basic Auth + +For sites using HTTP Basic Authentication: + +```bash +# Set credentials before navigation +agent-browser set credentials username password + +# Navigate to protected resource +agent-browser open https://protected.example.com/api +``` + +## Cookie-Based Auth + +Manually set authentication cookies: + +```bash +# Set auth cookie +agent-browser cookies set session_token "abc123xyz" + +# Navigate to protected page +agent-browser open https://app.example.com/dashboard +``` + +## Token Refresh Handling + +For sessions with expiring tokens: + +```bash +#!/bin/bash +# Wrapper that handles token refresh + +STATE_FILE="./auth-state.json" + +# Try loading existing state +if [[ -f "$STATE_FILE" ]]; then + agent-browser state load "$STATE_FILE" + agent-browser open https://app.example.com/dashboard + + # Check if session is still valid + URL=$(agent-browser get url) + if [[ "$URL" == *"/login"* ]]; then + echo "Session expired, re-authenticating..." + # Perform fresh login + agent-browser snapshot -i + agent-browser fill @e1 "$USERNAME" + agent-browser fill @e2 "$PASSWORD" + agent-browser click @e3 + agent-browser wait --url "**/dashboard" + agent-browser state save "$STATE_FILE" + fi +else + # First-time login + agent-browser open https://app.example.com/login + # ... login flow ... +fi +``` + +## Security Best Practices + +1. **Never commit state files** - They contain session tokens + ```bash + echo "*.auth-state.json" >> .gitignore + ``` + +2. **Use environment variables for credentials** + ```bash + agent-browser fill @e1 "$APP_USERNAME" + agent-browser fill @e2 "$APP_PASSWORD" + ``` + +3. **Clean up after automation** + ```bash + agent-browser cookies clear + rm -f ./auth-state.json + ``` + +4. **Use short-lived sessions for CI/CD** + ```bash + # Don't persist state in CI + agent-browser open https://app.example.com/login + # ... login and perform actions ... + agent-browser close # Session ends, nothing persisted + ``` diff --git a/.agents/skills/agent-browser/references/proxy-support.md b/.agents/skills/agent-browser/references/proxy-support.md new file mode 100644 index 0000000..05fcec2 --- /dev/null +++ b/.agents/skills/agent-browser/references/proxy-support.md @@ -0,0 +1,175 @@ +# Proxy Support + +Configure proxy servers for browser automation, useful for geo-testing, rate limiting avoidance, and corporate environments. + +## Basic Proxy Configuration + +Set proxy via environment variable before starting: + +```bash +# HTTP proxy +export HTTP_PROXY="http://proxy.example.com:8080" +agent-browser open https://example.com + +# HTTPS proxy +export HTTPS_PROXY="https://proxy.example.com:8080" +agent-browser open https://example.com + +# Both +export HTTP_PROXY="http://proxy.example.com:8080" +export HTTPS_PROXY="http://proxy.example.com:8080" +agent-browser open https://example.com +``` + +## Authenticated Proxy + +For proxies requiring authentication: + +```bash +# Include credentials in URL +export HTTP_PROXY="http://username:password@proxy.example.com:8080" +agent-browser open https://example.com +``` + +## SOCKS Proxy + +```bash +# SOCKS5 proxy +export ALL_PROXY="socks5://proxy.example.com:1080" +agent-browser open https://example.com + +# SOCKS5 with auth +export ALL_PROXY="socks5://user:pass@proxy.example.com:1080" +agent-browser open https://example.com +``` + +## Proxy Bypass + +Skip proxy for specific domains: + +```bash +# Bypass proxy for local addresses +export NO_PROXY="localhost,127.0.0.1,.internal.company.com" +agent-browser open https://internal.company.com # Direct connection +agent-browser open https://external.com # Via proxy +``` + +## Common Use Cases + +### Geo-Location Testing + +```bash +#!/bin/bash +# Test site from different regions using geo-located proxies + +PROXIES=( + "http://us-proxy.example.com:8080" + "http://eu-proxy.example.com:8080" + "http://asia-proxy.example.com:8080" +) + +for proxy in "${PROXIES[@]}"; do + export HTTP_PROXY="$proxy" + export HTTPS_PROXY="$proxy" + + region=$(echo "$proxy" | grep -oP '^\w+-\w+') + echo "Testing from: $region" + + agent-browser --session "$region" open https://example.com + agent-browser --session "$region" screenshot "./screenshots/$region.png" + agent-browser --session "$region" close +done +``` + +### Rotating Proxies for Scraping + +```bash +#!/bin/bash +# Rotate through proxy list to avoid rate limiting + +PROXY_LIST=( + "http://proxy1.example.com:8080" + "http://proxy2.example.com:8080" + "http://proxy3.example.com:8080" +) + +URLS=( + "https://site.com/page1" + "https://site.com/page2" + "https://site.com/page3" +) + +for i in "${!URLS[@]}"; do + proxy_index=$((i % ${#PROXY_LIST[@]})) + export HTTP_PROXY="${PROXY_LIST[$proxy_index]}" + export HTTPS_PROXY="${PROXY_LIST[$proxy_index]}" + + agent-browser open "${URLS[$i]}" + agent-browser get text body > "output-$i.txt" + agent-browser close + + sleep 1 # Polite delay +done +``` + +### Corporate Network Access + +```bash +#!/bin/bash +# Access internal sites via corporate proxy + +export HTTP_PROXY="http://corpproxy.company.com:8080" +export HTTPS_PROXY="http://corpproxy.company.com:8080" +export NO_PROXY="localhost,127.0.0.1,.company.com" + +# External sites go through proxy +agent-browser open https://external-vendor.com + +# Internal sites bypass proxy +agent-browser open https://intranet.company.com +``` + +## Verifying Proxy Connection + +```bash +# Check your apparent IP +agent-browser open https://httpbin.org/ip +agent-browser get text body +# Should show proxy's IP, not your real IP +``` + +## Troubleshooting + +### Proxy Connection Failed + +```bash +# Test proxy connectivity first +curl -x http://proxy.example.com:8080 https://httpbin.org/ip + +# Check if proxy requires auth +export HTTP_PROXY="http://user:pass@proxy.example.com:8080" +``` + +### SSL/TLS Errors Through Proxy + +Some proxies perform SSL inspection. If you encounter certificate errors: + +```bash +# For testing only - not recommended for production +agent-browser open https://example.com --ignore-https-errors +``` + +### Slow Performance + +```bash +# Use proxy only when necessary +export NO_PROXY="*.cdn.com,*.static.com" # Direct CDN access +``` + +## Best Practices + +1. **Use environment variables** - Don't hardcode proxy credentials +2. **Set NO_PROXY appropriately** - Avoid routing local traffic through proxy +3. **Test proxy before automation** - Verify connectivity with simple requests +4. **Handle proxy failures gracefully** - Implement retry logic for unstable proxies +5. **Rotate proxies for large scraping jobs** - Distribute load and avoid bans diff --git a/.agents/skills/agent-browser/references/session-management.md b/.agents/skills/agent-browser/references/session-management.md new file mode 100644 index 0000000..cfc3362 --- /dev/null +++ b/.agents/skills/agent-browser/references/session-management.md @@ -0,0 +1,181 @@ +# Session Management + +Run multiple isolated browser sessions concurrently with state persistence. + +## Named Sessions + +Use `--session` flag to isolate browser contexts: + +```bash +# Session 1: Authentication flow +agent-browser --session auth open https://app.example.com/login + +# Session 2: Public browsing (separate cookies, storage) +agent-browser --session public open https://example.com + +# Commands are isolated by session +agent-browser --session auth fill @e1 "user@example.com" +agent-browser --session public get text body +``` + +## Session Isolation Properties + +Each session has independent: +- Cookies +- LocalStorage / SessionStorage +- IndexedDB +- Cache +- Browsing history +- Open tabs + +## Session State Persistence + +### Save Session State + +```bash +# Save cookies, storage, and auth state +agent-browser state save /path/to/auth-state.json +``` + +### Load Session State + +```bash +# Restore saved state +agent-browser state load /path/to/auth-state.json + +# Continue with authenticated session +agent-browser open https://app.example.com/dashboard +``` + +### State File Contents + +```json +{ + "cookies": [...], + "localStorage": {...}, + "sessionStorage": {...}, + "origins": [...] +} +``` + +## Common Patterns + +### Authenticated Session Reuse + +```bash +#!/bin/bash +# Save login state once, reuse many times + +STATE_FILE="/tmp/auth-state.json" + +# Check if we have saved state +if [[ -f "$STATE_FILE" ]]; then + agent-browser state load "$STATE_FILE" + agent-browser open https://app.example.com/dashboard +else + # Perform login + agent-browser open https://app.example.com/login + agent-browser snapshot -i + agent-browser fill @e1 "$USERNAME" + agent-browser fill @e2 "$PASSWORD" + agent-browser click @e3 + agent-browser wait --load networkidle + + # Save for future use + agent-browser state save "$STATE_FILE" +fi +``` + +### Concurrent Scraping + +```bash +#!/bin/bash +# Scrape multiple sites concurrently + +# Start all sessions +agent-browser --session site1 open https://site1.com & +agent-browser --session site2 open https://site2.com & +agent-browser --session site3 open https://site3.com & +wait + +# Extract from each +agent-browser --session site1 get text body > site1.txt +agent-browser --session site2 get text body > site2.txt +agent-browser --session site3 get text body > site3.txt + +# Cleanup +agent-browser --session site1 close +agent-browser --session site2 close +agent-browser --session site3 close +``` + +### A/B Testing Sessions + +```bash +# Test different user experiences +agent-browser --session variant-a open "https://app.com?variant=a" +agent-browser --session variant-b open "https://app.com?variant=b" + +# Compare +agent-browser --session variant-a screenshot /tmp/variant-a.png +agent-browser --session variant-b screenshot /tmp/variant-b.png +``` + +## Default Session + +When `--session` is omitted, commands use the default session: + +```bash +# These use the same default session +agent-browser open https://example.com +agent-browser snapshot -i +agent-browser close # Closes default session +``` + +## Session Cleanup + +```bash +# Close specific session +agent-browser --session auth close + +# List active sessions +agent-browser session list +``` + +## Best Practices + +### 1. Name Sessions Semantically + +```bash +# GOOD: Clear purpose +agent-browser --session github-auth open https://github.com +agent-browser --session docs-scrape open https://docs.example.com + +# AVOID: Generic names +agent-browser --session s1 open https://github.com +``` + +### 2. Always Clean Up + +```bash +# Close sessions when done +agent-browser --session auth close +agent-browser --session scrape close +``` + +### 3. Handle State Files Securely + +```bash +# Don't commit state files (contain auth tokens!) +echo "*.auth-state.json" >> .gitignore + +# Delete after use +rm /tmp/auth-state.json +``` + +### 4. Timeout Long Sessions + +```bash +# Set timeout for automated scripts +timeout 60 agent-browser --session long-task get text body +``` diff --git a/.agents/skills/agent-browser/references/snapshot-refs.md b/.agents/skills/agent-browser/references/snapshot-refs.md new file mode 100644 index 0000000..0b17a4d --- /dev/null +++ b/.agents/skills/agent-browser/references/snapshot-refs.md @@ -0,0 +1,186 @@ +# Snapshot + Refs Workflow + +The core innovation of agent-browser: compact element references that reduce context usage dramatically for AI agents. + +## How It Works + +### The Problem +Traditional browser automation sends full DOM to AI agents: +``` +Full DOM/HTML sent → AI parses → Generates CSS selector → Executes action +~3000-5000 tokens per interaction +``` + +### The Solution +agent-browser uses compact snapshots with refs: +``` +Compact snapshot → @refs assigned → Direct ref interaction +~200-400 tokens per interaction +``` + +## The Snapshot Command + +```bash +# Basic snapshot (shows page structure) +agent-browser snapshot + +# Interactive snapshot (-i flag) - RECOMMENDED +agent-browser snapshot -i +``` + +### Snapshot Output Format + +``` +Page: Example Site - Home +URL: https://example.com + +@e1 [header] + @e2 [nav] + @e3 [a] "Home" + @e4 [a] "Products" + @e5 [a] "About" + @e6 [button] "Sign In" + +@e7 [main] + @e8 [h1] "Welcome" + @e9 [form] + @e10 [input type="email"] placeholder="Email" + @e11 [input type="password"] placeholder="Password" + @e12 [button type="submit"] "Log In" + +@e13 [footer] + @e14 [a] "Privacy Policy" +``` + +## Using Refs + +Once you have refs, interact directly: + +```bash +# Click the "Sign In" button +agent-browser click @e6 + +# Fill email input +agent-browser fill @e10 "user@example.com" + +# Fill password +agent-browser fill @e11 "password123" + +# Submit the form +agent-browser click @e12 +``` + +## Ref Lifecycle + +**IMPORTANT**: Refs are invalidated when the page changes! + +```bash +# Get initial snapshot +agent-browser snapshot -i +# @e1 [button] "Next" + +# Click triggers page change +agent-browser click @e1 + +# MUST re-snapshot to get new refs! +agent-browser snapshot -i +# @e1 [h1] "Page 2" ← Different element now! +``` + +## Best Practices + +### 1. Always Snapshot Before Interacting + +```bash +# CORRECT +agent-browser open https://example.com +agent-browser snapshot -i # Get refs first +agent-browser click @e1 # Use ref + +# WRONG +agent-browser open https://example.com +agent-browser click @e1 # Ref doesn't exist yet! +``` + +### 2. Re-Snapshot After Navigation + +```bash +agent-browser click @e5 # Navigates to new page +agent-browser snapshot -i # Get new refs +agent-browser click @e1 # Use new refs +``` + +### 3. Re-Snapshot After Dynamic Changes + +```bash +agent-browser click @e1 # Opens dropdown +agent-browser snapshot -i # See dropdown items +agent-browser click @e7 # Select item +``` + +### 4. Snapshot Specific Regions + +For complex pages, snapshot specific areas: + +```bash +# Snapshot just the form +agent-browser snapshot @e9 +``` + +## Ref Notation Details + +``` +@e1 [tag type="value"] "text content" placeholder="hint" +│ │ │ │ │ +│ │ │ │ └─ Additional attributes +│ │ │ └─ Visible text +│ │ └─ Key attributes shown +│ └─ HTML tag name +└─ Unique ref ID +``` + +### Common Patterns + +``` +@e1 [button] "Submit" # Button with text +@e2 [input type="email"] # Email input +@e3 [input type="password"] # Password input +@e4 [a href="/page"] "Link Text" # Anchor link +@e5 [select] # Dropdown +@e6 [textarea] placeholder="Message" # Text area +@e7 [div class="modal"] # Container (when relevant) +@e8 [img alt="Logo"] # Image +@e9 [checkbox] checked # Checked checkbox +@e10 [radio] selected # Selected radio +``` + +## Troubleshooting + +### "Ref not found" Error + +```bash +# Ref may have changed - re-snapshot +agent-browser snapshot -i +``` + +### Element Not Visible in Snapshot + +```bash +# Scroll to reveal element +agent-browser scroll --bottom +agent-browser snapshot -i + +# Or wait for dynamic content +agent-browser wait 1000 +agent-browser snapshot -i +``` + +### Too Many Elements + +```bash +# Snapshot specific container +agent-browser snapshot @e5 + +# Or use get text for content-only extraction +agent-browser get text @e5 +``` diff --git a/.agents/skills/agent-browser/references/video-recording.md b/.agents/skills/agent-browser/references/video-recording.md new file mode 100644 index 0000000..98e6b0a --- /dev/null +++ b/.agents/skills/agent-browser/references/video-recording.md @@ -0,0 +1,162 @@ +# Video Recording + +Capture browser automation sessions as video for debugging, documentation, or verification. + +## Basic Recording + +```bash +# Start recording +agent-browser record start ./demo.webm + +# Perform actions +agent-browser open https://example.com +agent-browser snapshot -i +agent-browser click @e1 +agent-browser fill @e2 "test input" + +# Stop and save +agent-browser record stop +``` + +## Recording Commands + +```bash +# Start recording to file +agent-browser record start ./output.webm + +# Stop current recording +agent-browser record stop + +# Restart with new file (stops current + starts new) +agent-browser record restart ./take2.webm +``` + +## Use Cases + +### Debugging Failed Automation + +```bash +#!/bin/bash +# Record automation for debugging + +agent-browser record start ./debug-$(date +%Y%m%d-%H%M%S).webm + +# Run your automation +agent-browser open https://app.example.com +agent-browser snapshot -i +agent-browser click @e1 || { + echo "Click failed - check recording" + agent-browser record stop + exit 1 +} + +agent-browser record stop +``` + +### Documentation Generation + +```bash +#!/bin/bash +# Record workflow for documentation + +agent-browser record start ./docs/how-to-login.webm + +agent-browser open https://app.example.com/login +agent-browser wait 1000 # Pause for visibility + +agent-browser snapshot -i +agent-browser fill @e1 "demo@example.com" +agent-browser wait 500 + +agent-browser fill @e2 "password" +agent-browser wait 500 + +agent-browser click @e3 +agent-browser wait --load networkidle +agent-browser wait 1000 # Show result + +agent-browser record stop +``` + +### CI/CD Test Evidence + +```bash +#!/bin/bash +# Record E2E test runs for CI artifacts + +TEST_NAME="${1:-e2e-test}" +RECORDING_DIR="./test-recordings" +mkdir -p "$RECORDING_DIR" + +agent-browser record start "$RECORDING_DIR/$TEST_NAME-$(date +%s).webm" + +# Run test +if run_e2e_test; then + echo "Test passed" +else + echo "Test failed - recording saved" +fi + +agent-browser record stop +``` + +## Best Practices + +### 1. Add Pauses for Clarity + +```bash +# Slow down for human viewing +agent-browser click @e1 +agent-browser wait 500 # Let viewer see result +``` + +### 2. Use Descriptive Filenames + +```bash +# Include context in filename +agent-browser record start ./recordings/login-flow-2024-01-15.webm +agent-browser record start ./recordings/checkout-test-run-42.webm +``` + +### 3. Handle Recording in Error Cases + +```bash +#!/bin/bash +set -e + +cleanup() { + agent-browser record stop 2>/dev/null || true + agent-browser close 2>/dev/null || true +} +trap cleanup EXIT + +agent-browser record start ./automation.webm +# ... automation steps ... +``` + +### 4. Combine with Screenshots + +```bash +# Record video AND capture key frames +agent-browser record start ./flow.webm + +agent-browser open https://example.com +agent-browser screenshot ./screenshots/step1-homepage.png + +agent-browser click @e1 +agent-browser screenshot ./screenshots/step2-after-click.png + +agent-browser record stop +``` + +## Output Format + +- Default format: WebM (VP8/VP9 codec) +- Compatible with all modern browsers and video players +- Compressed but high quality + +## Limitations + +- Recording adds slight overhead to automation +- Large recordings can consume significant disk space +- Some headless environments may have codec limitations diff --git a/.agents/skills/agent-browser/templates/authenticated-session.sh b/.agents/skills/agent-browser/templates/authenticated-session.sh new file mode 100755 index 0000000..e44aaad --- /dev/null +++ b/.agents/skills/agent-browser/templates/authenticated-session.sh @@ -0,0 +1,91 @@ +#!/bin/bash +# Template: Authenticated Session Workflow +# Login once, save state, reuse for subsequent runs +# +# Usage: +# ./authenticated-session.sh [state-file] +# +# Setup: +# 1. Run once to see your form structure +# 2. Note the @refs for your fields +# 3. Uncomment LOGIN FLOW section and update refs + +set -euo pipefail + +LOGIN_URL="${1:?Usage: $0 [state-file]}" +STATE_FILE="${2:-./auth-state.json}" + +echo "Authentication workflow for: $LOGIN_URL" + +# ══════════════════════════════════════════════════════════════ +# SAVED STATE: Skip login if we have valid saved state +# ══════════════════════════════════════════════════════════════ +if [[ -f "$STATE_FILE" ]]; then + echo "Loading saved authentication state..." + agent-browser state load "$STATE_FILE" + agent-browser open "$LOGIN_URL" + agent-browser wait --load networkidle + + CURRENT_URL=$(agent-browser get url) + if [[ "$CURRENT_URL" != *"login"* ]] && [[ "$CURRENT_URL" != *"signin"* ]]; then + echo "Session restored successfully!" + agent-browser snapshot -i + exit 0 + fi + echo "Session expired, performing fresh login..." + rm -f "$STATE_FILE" +fi + +# ══════════════════════════════════════════════════════════════ +# DISCOVERY MODE: Show form structure (remove after setup) +# ══════════════════════════════════════════════════════════════ +echo "Opening login page..." +agent-browser open "$LOGIN_URL" +agent-browser wait --load networkidle + +echo "" +echo "┌─────────────────────────────────────────────────────────┐" +echo "│ LOGIN FORM STRUCTURE │" +echo "├─────────────────────────────────────────────────────────┤" +agent-browser snapshot -i +echo "└─────────────────────────────────────────────────────────┘" +echo "" +echo "Next steps:" +echo " 1. Note refs: @e? = username, @e? = password, @e? = submit" +echo " 2. Uncomment LOGIN FLOW section below" +echo " 3. Replace @e1, @e2, @e3 with your refs" +echo " 4. Delete this DISCOVERY MODE section" +echo "" +agent-browser close +exit 0 + +# ══════════════════════════════════════════════════════════════ +# LOGIN FLOW: Uncomment and customize after discovery +# ══════════════════════════════════════════════════════════════ +# : "${APP_USERNAME:?Set APP_USERNAME environment variable}" +# : "${APP_PASSWORD:?Set APP_PASSWORD environment variable}" +# +# agent-browser open "$LOGIN_URL" +# agent-browser wait --load networkidle +# agent-browser snapshot -i +# +# # Fill credentials (update refs to match your form) +# agent-browser fill @e1 "$APP_USERNAME" +# agent-browser fill @e2 "$APP_PASSWORD" +# agent-browser click @e3 +# agent-browser wait --load networkidle +# +# # Verify login succeeded +# FINAL_URL=$(agent-browser get url) +# if [[ "$FINAL_URL" == *"login"* ]] || [[ "$FINAL_URL" == *"signin"* ]]; then +# echo "ERROR: Login failed - still on login page" +# agent-browser screenshot /tmp/login-failed.png +# agent-browser close +# exit 1 +# fi +# +# # Save state for future runs +# echo "Saving authentication state to: $STATE_FILE" +# agent-browser state save "$STATE_FILE" +# echo "Login successful!" +# agent-browser snapshot -i diff --git a/.agents/skills/agent-browser/templates/capture-workflow.sh b/.agents/skills/agent-browser/templates/capture-workflow.sh new file mode 100755 index 0000000..a4eae75 --- /dev/null +++ b/.agents/skills/agent-browser/templates/capture-workflow.sh @@ -0,0 +1,68 @@ +#!/bin/bash +# Template: Content Capture Workflow +# Extract content from web pages with optional authentication + +set -euo pipefail + +TARGET_URL="${1:?Usage: $0 [output-dir]}" +OUTPUT_DIR="${2:-.}" + +echo "Capturing content from: $TARGET_URL" +mkdir -p "$OUTPUT_DIR" + +# Optional: Load authentication state if needed +# if [[ -f "./auth-state.json" ]]; then +# agent-browser state load "./auth-state.json" +# fi + +# Navigate to target page +agent-browser open "$TARGET_URL" +agent-browser wait --load networkidle + +# Get page metadata +echo "Page title: $(agent-browser get title)" +echo "Page URL: $(agent-browser get url)" + +# Capture full page screenshot +agent-browser screenshot --full "$OUTPUT_DIR/page-full.png" +echo "Screenshot saved: $OUTPUT_DIR/page-full.png" + +# Get page structure +agent-browser snapshot -i > "$OUTPUT_DIR/page-structure.txt" +echo "Structure saved: $OUTPUT_DIR/page-structure.txt" + +# Extract main content +# Adjust selector based on target site structure +# agent-browser get text @e1 > "$OUTPUT_DIR/main-content.txt" + +# Extract specific elements (uncomment as needed) +# agent-browser get text "article" > "$OUTPUT_DIR/article.txt" +# agent-browser get text "main" > "$OUTPUT_DIR/main.txt" +# agent-browser get text ".content" > "$OUTPUT_DIR/content.txt" + +# Get full page text +agent-browser get text body > "$OUTPUT_DIR/page-text.txt" +echo "Text content saved: $OUTPUT_DIR/page-text.txt" + +# Optional: Save as PDF +agent-browser pdf "$OUTPUT_DIR/page.pdf" +echo "PDF saved: $OUTPUT_DIR/page.pdf" + +# Optional: Capture with scrolling for infinite scroll pages +# scroll_and_capture() { +# local count=0 +# while [[ $count -lt 5 ]]; do +# agent-browser scroll down 1000 +# agent-browser wait 1000 +# ((count++)) +# done +# agent-browser screenshot --full "$OUTPUT_DIR/page-scrolled.png" +# } +# scroll_and_capture + +# Cleanup +agent-browser close + +echo "" +echo "Capture complete! Files saved to: $OUTPUT_DIR" +ls -la "$OUTPUT_DIR" diff --git a/.agents/skills/agent-browser/templates/form-automation.sh b/.agents/skills/agent-browser/templates/form-automation.sh new file mode 100755 index 0000000..02a7c81 --- /dev/null +++ b/.agents/skills/agent-browser/templates/form-automation.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# Template: Form Automation Workflow +# Fills and submits web forms with validation + +set -euo pipefail + +FORM_URL="${1:?Usage: $0 }" + +echo "Automating form at: $FORM_URL" + +# Navigate to form page +agent-browser open "$FORM_URL" +agent-browser wait --load networkidle + +# Get interactive snapshot to identify form fields +echo "Analyzing form structure..." +agent-browser snapshot -i + +# Example: Fill common form fields +# Uncomment and modify refs based on snapshot output + +# Text inputs +# agent-browser fill @e1 "John Doe" # Name field +# agent-browser fill @e2 "user@example.com" # Email field +# agent-browser fill @e3 "+1-555-123-4567" # Phone field + +# Password fields +# agent-browser fill @e4 "SecureP@ssw0rd!" + +# Dropdowns +# agent-browser select @e5 "Option Value" + +# Checkboxes +# agent-browser check @e6 # Check +# agent-browser uncheck @e7 # Uncheck + +# Radio buttons +# agent-browser click @e8 # Select radio option + +# Text areas +# agent-browser fill @e9 "Multi-line text content here" + +# File uploads +# agent-browser upload @e10 /path/to/file.pdf + +# Submit form +# agent-browser click @e11 # Submit button + +# Wait for response +# agent-browser wait --load networkidle +# agent-browser wait --url "**/success" # Or wait for redirect + +# Verify submission +echo "Form submission result:" +agent-browser get url +agent-browser snapshot -i + +# Take screenshot of result +agent-browser screenshot /tmp/form-result.png + +# Cleanup +agent-browser close + +echo "Form automation complete" diff --git a/.agents/skills/frontend-design/LICENSE.txt b/.agents/skills/frontend-design/LICENSE.txt deleted file mode 100644 index f433b1a..0000000 --- a/.agents/skills/frontend-design/LICENSE.txt +++ /dev/null @@ -1,177 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS diff --git a/.agents/skills/frontend-design/SKILL.md b/.agents/skills/frontend-design/SKILL.md deleted file mode 100644 index 5be498e..0000000 --- a/.agents/skills/frontend-design/SKILL.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -name: frontend-design -description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics. -license: Complete terms in LICENSE.txt ---- - -This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices. - -The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints. - -## Design Thinking - -Before coding, understand the context and commit to a BOLD aesthetic direction: -- **Purpose**: What problem does this interface solve? Who uses it? -- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction. -- **Constraints**: Technical requirements (framework, performance, accessibility). -- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember? - -**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity. - -Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is: -- Production-grade and functional -- Visually striking and memorable -- Cohesive with a clear aesthetic point-of-view -- Meticulously refined in every detail - -## Frontend Aesthetics Guidelines - -Focus on: -- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font. -- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes. -- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise. -- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density. -- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays. - -NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character. - -Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations. - -**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well. - -Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision. diff --git a/.agents/skills/vercel-react-best-practices/AGENTS.md b/.agents/skills/vercel-react-best-practices/AGENTS.md deleted file mode 100644 index e53dde1..0000000 --- a/.agents/skills/vercel-react-best-practices/AGENTS.md +++ /dev/null @@ -1,2934 +0,0 @@ -# React Best Practices - -**Version 1.0.0** -Vercel Engineering -January 2026 - -> **Note:** -> This document is mainly for agents and LLMs to follow when maintaining, -> generating, or refactoring React and Next.js codebases at Vercel. Humans -> may also find it useful, but guidance here is optimized for automation -> and consistency by AI-assisted workflows. - ---- - -## Abstract - -Comprehensive performance optimization guide for React and Next.js applications, designed for AI agents and LLMs. Contains 40+ rules across 8 categories, prioritized by impact from critical (eliminating waterfalls, reducing bundle size) to incremental (advanced patterns). Each rule includes detailed explanations, real-world examples comparing incorrect vs. correct implementations, and specific impact metrics to guide automated refactoring and code generation. - ---- - -## Table of Contents - -1. [Eliminating Waterfalls](#1-eliminating-waterfalls) — **CRITICAL** - - 1.1 [Defer Await Until Needed](#11-defer-await-until-needed) - - 1.2 [Dependency-Based Parallelization](#12-dependency-based-parallelization) - - 1.3 [Prevent Waterfall Chains in API Routes](#13-prevent-waterfall-chains-in-api-routes) - - 1.4 [Promise.all() for Independent Operations](#14-promiseall-for-independent-operations) - - 1.5 [Strategic Suspense Boundaries](#15-strategic-suspense-boundaries) -2. [Bundle Size Optimization](#2-bundle-size-optimization) — **CRITICAL** - - 2.1 [Avoid Barrel File Imports](#21-avoid-barrel-file-imports) - - 2.2 [Conditional Module Loading](#22-conditional-module-loading) - - 2.3 [Defer Non-Critical Third-Party Libraries](#23-defer-non-critical-third-party-libraries) - - 2.4 [Dynamic Imports for Heavy Components](#24-dynamic-imports-for-heavy-components) - - 2.5 [Preload Based on User Intent](#25-preload-based-on-user-intent) -3. [Server-Side Performance](#3-server-side-performance) — **HIGH** - - 3.1 [Authenticate Server Actions Like API Routes](#31-authenticate-server-actions-like-api-routes) - - 3.2 [Avoid Duplicate Serialization in RSC Props](#32-avoid-duplicate-serialization-in-rsc-props) - - 3.3 [Cross-Request LRU Caching](#33-cross-request-lru-caching) - - 3.4 [Minimize Serialization at RSC Boundaries](#34-minimize-serialization-at-rsc-boundaries) - - 3.5 [Parallel Data Fetching with Component Composition](#35-parallel-data-fetching-with-component-composition) - - 3.6 [Per-Request Deduplication with React.cache()](#36-per-request-deduplication-with-reactcache) - - 3.7 [Use after() for Non-Blocking Operations](#37-use-after-for-non-blocking-operations) -4. [Client-Side Data Fetching](#4-client-side-data-fetching) — **MEDIUM-HIGH** - - 4.1 [Deduplicate Global Event Listeners](#41-deduplicate-global-event-listeners) - - 4.2 [Use Passive Event Listeners for Scrolling Performance](#42-use-passive-event-listeners-for-scrolling-performance) - - 4.3 [Use SWR for Automatic Deduplication](#43-use-swr-for-automatic-deduplication) - - 4.4 [Version and Minimize localStorage Data](#44-version-and-minimize-localstorage-data) -5. [Re-render Optimization](#5-re-render-optimization) — **MEDIUM** - - 5.1 [Calculate Derived State During Rendering](#51-calculate-derived-state-during-rendering) - - 5.2 [Defer State Reads to Usage Point](#52-defer-state-reads-to-usage-point) - - 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) - - 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) - - 5.5 [Extract to Memoized Components](#55-extract-to-memoized-components) - - 5.6 [Narrow Effect Dependencies](#56-narrow-effect-dependencies) - - 5.7 [Put Interaction Logic in Event Handlers](#57-put-interaction-logic-in-event-handlers) - - 5.8 [Subscribe to Derived State](#58-subscribe-to-derived-state) - - 5.9 [Use Functional setState Updates](#59-use-functional-setstate-updates) - - 5.10 [Use Lazy State Initialization](#510-use-lazy-state-initialization) - - 5.11 [Use Transitions for Non-Urgent Updates](#511-use-transitions-for-non-urgent-updates) - - 5.12 [Use useRef for Transient Values](#512-use-useref-for-transient-values) -6. [Rendering Performance](#6-rendering-performance) — **MEDIUM** - - 6.1 [Animate SVG Wrapper Instead of SVG Element](#61-animate-svg-wrapper-instead-of-svg-element) - - 6.2 [CSS content-visibility for Long Lists](#62-css-content-visibility-for-long-lists) - - 6.3 [Hoist Static JSX Elements](#63-hoist-static-jsx-elements) - - 6.4 [Optimize SVG Precision](#64-optimize-svg-precision) - - 6.5 [Prevent Hydration Mismatch Without Flickering](#65-prevent-hydration-mismatch-without-flickering) - - 6.6 [Suppress Expected Hydration Mismatches](#66-suppress-expected-hydration-mismatches) - - 6.7 [Use Activity Component for Show/Hide](#67-use-activity-component-for-showhide) - - 6.8 [Use Explicit Conditional Rendering](#68-use-explicit-conditional-rendering) - - 6.9 [Use useTransition Over Manual Loading States](#69-use-usetransition-over-manual-loading-states) -7. [JavaScript Performance](#7-javascript-performance) — **LOW-MEDIUM** - - 7.1 [Avoid Layout Thrashing](#71-avoid-layout-thrashing) - - 7.2 [Build Index Maps for Repeated Lookups](#72-build-index-maps-for-repeated-lookups) - - 7.3 [Cache Property Access in Loops](#73-cache-property-access-in-loops) - - 7.4 [Cache Repeated Function Calls](#74-cache-repeated-function-calls) - - 7.5 [Cache Storage API Calls](#75-cache-storage-api-calls) - - 7.6 [Combine Multiple Array Iterations](#76-combine-multiple-array-iterations) - - 7.7 [Early Length Check for Array Comparisons](#77-early-length-check-for-array-comparisons) - - 7.8 [Early Return from Functions](#78-early-return-from-functions) - - 7.9 [Hoist RegExp Creation](#79-hoist-regexp-creation) - - 7.10 [Use Loop for Min/Max Instead of Sort](#710-use-loop-for-minmax-instead-of-sort) - - 7.11 [Use Set/Map for O(1) Lookups](#711-use-setmap-for-o1-lookups) - - 7.12 [Use toSorted() Instead of sort() for Immutability](#712-use-tosorted-instead-of-sort-for-immutability) -8. [Advanced Patterns](#8-advanced-patterns) — **LOW** - - 8.1 [Initialize App Once, Not Per Mount](#81-initialize-app-once-not-per-mount) - - 8.2 [Store Event Handlers in Refs](#82-store-event-handlers-in-refs) - - 8.3 [useEffectEvent for Stable Callback Refs](#83-useeffectevent-for-stable-callback-refs) - ---- - -## 1. Eliminating Waterfalls - -**Impact: CRITICAL** - -Waterfalls are the #1 performance killer. Each sequential await adds full network latency. Eliminating them yields the largest gains. - -### 1.1 Defer Await Until Needed - -**Impact: HIGH (avoids blocking unused code paths)** - -Move `await` operations into the branches where they're actually used to avoid blocking code paths that don't need them. - -**Incorrect: blocks both branches** - -```typescript -async function handleRequest(userId: string, skipProcessing: boolean) { - const userData = await fetchUserData(userId) - - if (skipProcessing) { - // Returns immediately but still waited for userData - return { skipped: true } - } - - // Only this branch uses userData - return processUserData(userData) -} -``` - -**Correct: only blocks when needed** - -```typescript -async function handleRequest(userId: string, skipProcessing: boolean) { - if (skipProcessing) { - // Returns immediately without waiting - return { skipped: true } - } - - // Fetch only when needed - const userData = await fetchUserData(userId) - return processUserData(userData) -} -``` - -**Another example: early return optimization** - -```typescript -// Incorrect: always fetches permissions -async function updateResource(resourceId: string, userId: string) { - const permissions = await fetchPermissions(userId) - const resource = await getResource(resourceId) - - if (!resource) { - return { error: 'Not found' } - } - - if (!permissions.canEdit) { - return { error: 'Forbidden' } - } - - return await updateResourceData(resource, permissions) -} - -// Correct: fetches only when needed -async function updateResource(resourceId: string, userId: string) { - const resource = await getResource(resourceId) - - if (!resource) { - return { error: 'Not found' } - } - - const permissions = await fetchPermissions(userId) - - if (!permissions.canEdit) { - return { error: 'Forbidden' } - } - - return await updateResourceData(resource, permissions) -} -``` - -This optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive. - -### 1.2 Dependency-Based Parallelization - -**Impact: CRITICAL (2-10× improvement)** - -For operations with partial dependencies, use `better-all` to maximize parallelism. It automatically starts each task at the earliest possible moment. - -**Incorrect: profile waits for config unnecessarily** - -```typescript -const [user, config] = await Promise.all([ - fetchUser(), - fetchConfig() -]) -const profile = await fetchProfile(user.id) -``` - -**Correct: config and profile run in parallel** - -```typescript -import { all } from 'better-all' - -const { user, config, profile } = await all({ - async user() { return fetchUser() }, - async config() { return fetchConfig() }, - async profile() { - return fetchProfile((await this.$.user).id) - } -}) -``` - -**Alternative without extra dependencies:** - -```typescript -const userPromise = fetchUser() -const profilePromise = userPromise.then(user => fetchProfile(user.id)) - -const [user, config, profile] = await Promise.all([ - userPromise, - fetchConfig(), - profilePromise -]) -``` - -We can also create all the promises first, and do `Promise.all()` at the end. - -Reference: [https://github.com/shuding/better-all](https://github.com/shuding/better-all) - -### 1.3 Prevent Waterfall Chains in API Routes - -**Impact: CRITICAL (2-10× improvement)** - -In API routes and Server Actions, start independent operations immediately, even if you don't await them yet. - -**Incorrect: config waits for auth, data waits for both** - -```typescript -export async function GET(request: Request) { - const session = await auth() - const config = await fetchConfig() - const data = await fetchData(session.user.id) - return Response.json({ data, config }) -} -``` - -**Correct: auth and config start immediately** - -```typescript -export async function GET(request: Request) { - const sessionPromise = auth() - const configPromise = fetchConfig() - const session = await sessionPromise - const [config, data] = await Promise.all([ - configPromise, - fetchData(session.user.id) - ]) - return Response.json({ data, config }) -} -``` - -For operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization). - -### 1.4 Promise.all() for Independent Operations - -**Impact: CRITICAL (2-10× improvement)** - -When async operations have no interdependencies, execute them concurrently using `Promise.all()`. - -**Incorrect: sequential execution, 3 round trips** - -```typescript -const user = await fetchUser() -const posts = await fetchPosts() -const comments = await fetchComments() -``` - -**Correct: parallel execution, 1 round trip** - -```typescript -const [user, posts, comments] = await Promise.all([ - fetchUser(), - fetchPosts(), - fetchComments() -]) -``` - -### 1.5 Strategic Suspense Boundaries - -**Impact: HIGH (faster initial paint)** - -Instead of awaiting data in async components before returning JSX, use Suspense boundaries to show the wrapper UI faster while data loads. - -**Incorrect: wrapper blocked by data fetching** - -```tsx -async function Page() { - const data = await fetchData() // Blocks entire page - - return ( -

-
Sidebar
-
Header
-
- -
-
Footer
-
- ) -} -``` - -The entire layout waits for data even though only the middle section needs it. - -**Correct: wrapper shows immediately, data streams in** - -```tsx -function Page() { - return ( -
-
Sidebar
-
Header
-
- }> - - -
-
Footer
-
- ) -} - -async function DataDisplay() { - const data = await fetchData() // Only blocks this component - return
{data.content}
-} -``` - -Sidebar, Header, and Footer render immediately. Only DataDisplay waits for data. - -**Alternative: share promise across components** - -```tsx -function Page() { - // Start fetch immediately, but don't await - const dataPromise = fetchData() - - return ( -
-
Sidebar
-
Header
- }> - - - -
Footer
-
- ) -} - -function DataDisplay({ dataPromise }: { dataPromise: Promise }) { - const data = use(dataPromise) // Unwraps the promise - return
{data.content}
-} - -function DataSummary({ dataPromise }: { dataPromise: Promise }) { - const data = use(dataPromise) // Reuses the same promise - return
{data.summary}
-} -``` - -Both components share the same promise, so only one fetch occurs. Layout renders immediately while both components wait together. - -**When NOT to use this pattern:** - -- Critical data needed for layout decisions (affects positioning) - -- SEO-critical content above the fold - -- Small, fast queries where suspense overhead isn't worth it - -- When you want to avoid layout shift (loading → content jump) - -**Trade-off:** Faster initial paint vs potential layout shift. Choose based on your UX priorities. - ---- - -## 2. Bundle Size Optimization - -**Impact: CRITICAL** - -Reducing initial bundle size improves Time to Interactive and Largest Contentful Paint. - -### 2.1 Avoid Barrel File Imports - -**Impact: CRITICAL (200-800ms import cost, slow builds)** - -Import 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'`). - -Popular 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. - -**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. - -**Incorrect: imports entire library** - -```tsx -import { Check, X, Menu } from 'lucide-react' -// Loads 1,583 modules, takes ~2.8s extra in dev -// Runtime cost: 200-800ms on every cold start - -import { Button, TextField } from '@mui/material' -// Loads 2,225 modules, takes ~4.2s extra in dev -``` - -**Correct: imports only what you need** - -```tsx -import Check from 'lucide-react/dist/esm/icons/check' -import X from 'lucide-react/dist/esm/icons/x' -import Menu from 'lucide-react/dist/esm/icons/menu' -// Loads only 3 modules (~2KB vs ~1MB) - -import Button from '@mui/material/Button' -import TextField from '@mui/material/TextField' -// Loads only what you use -``` - -**Alternative: Next.js 13.5+** - -```js -// next.config.js - use optimizePackageImports -module.exports = { - experimental: { - optimizePackageImports: ['lucide-react', '@mui/material'] - } -} - -// Then you can keep the ergonomic barrel imports: -import { Check, X, Menu } from 'lucide-react' -// Automatically transformed to direct imports at build time -``` - -Direct imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR. - -Libraries 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`. - -Reference: [https://vercel.com/blog/how-we-optimized-package-imports-in-next-js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js) - -### 2.2 Conditional Module Loading - -**Impact: HIGH (loads large data only when needed)** - -Load large data or modules only when a feature is activated. - -**Example: lazy-load animation frames** - -```tsx -function AnimationPlayer({ enabled, setEnabled }: { enabled: boolean; setEnabled: React.Dispatch> }) { - const [frames, setFrames] = useState(null) - - useEffect(() => { - if (enabled && !frames && typeof window !== 'undefined') { - import('./animation-frames.js') - .then(mod => setFrames(mod.frames)) - .catch(() => setEnabled(false)) - } - }, [enabled, frames, setEnabled]) - - if (!frames) return - return -} -``` - -The `typeof window !== 'undefined'` check prevents bundling this module for SSR, optimizing server bundle size and build speed. - -### 2.3 Defer Non-Critical Third-Party Libraries - -**Impact: MEDIUM (loads after hydration)** - -Analytics, logging, and error tracking don't block user interaction. Load them after hydration. - -**Incorrect: blocks initial bundle** - -```tsx -import { Analytics } from '@vercel/analytics/react' - -export default function RootLayout({ children }) { - return ( - - - {children} - - - - ) -} -``` - -**Correct: loads after hydration** - -```tsx -import dynamic from 'next/dynamic' - -const Analytics = dynamic( - () => import('@vercel/analytics/react').then(m => m.Analytics), - { ssr: false } -) - -export default function RootLayout({ children }) { - return ( - - - {children} - - - - ) -} -``` - -### 2.4 Dynamic Imports for Heavy Components - -**Impact: CRITICAL (directly affects TTI and LCP)** - -Use `next/dynamic` to lazy-load large components not needed on initial render. - -**Incorrect: Monaco bundles with main chunk ~300KB** - -```tsx -import { MonacoEditor } from './monaco-editor' - -function CodePanel({ code }: { code: string }) { - return -} -``` - -**Correct: Monaco loads on demand** - -```tsx -import dynamic from 'next/dynamic' - -const MonacoEditor = dynamic( - () => import('./monaco-editor').then(m => m.MonacoEditor), - { ssr: false } -) - -function CodePanel({ code }: { code: string }) { - return -} -``` - -### 2.5 Preload Based on User Intent - -**Impact: MEDIUM (reduces perceived latency)** - -Preload heavy bundles before they're needed to reduce perceived latency. - -**Example: preload on hover/focus** - -```tsx -function EditorButton({ onClick }: { onClick: () => void }) { - const preload = () => { - if (typeof window !== 'undefined') { - void import('./monaco-editor') - } - } - - return ( - - ) -} -``` - -**Example: preload when feature flag is enabled** - -```tsx -function FlagsProvider({ children, flags }: Props) { - useEffect(() => { - if (flags.editorEnabled && typeof window !== 'undefined') { - void import('./monaco-editor').then(mod => mod.init()) - } - }, [flags.editorEnabled]) - - return - {children} - -} -``` - -The `typeof window !== 'undefined'` check prevents bundling preloaded modules for SSR, optimizing server bundle size and build speed. - ---- - -## 3. Server-Side Performance - -**Impact: HIGH** - -Optimizing server-side rendering and data fetching eliminates server-side waterfalls and reduces response times. - -### 3.1 Authenticate Server Actions Like API Routes - -**Impact: CRITICAL (prevents unauthorized access to server mutations)** - -Server 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. - -Next.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." - -**Incorrect: no authentication check** - -```typescript -'use server' - -export async function deleteUser(userId: string) { - // Anyone can call this! No auth check - await db.user.delete({ where: { id: userId } }) - return { success: true } -} -``` - -**Correct: authentication inside the action** - -```typescript -'use server' - -import { verifySession } from '@/lib/auth' -import { unauthorized } from '@/lib/errors' - -export async function deleteUser(userId: string) { - // Always check auth inside the action - const session = await verifySession() - - if (!session) { - throw unauthorized('Must be logged in') - } - - // Check authorization too - if (session.user.role !== 'admin' && session.user.id !== userId) { - throw unauthorized('Cannot delete other users') - } - - await db.user.delete({ where: { id: userId } }) - return { success: true } -} -``` - -**With input validation:** - -```typescript -'use server' - -import { verifySession } from '@/lib/auth' -import { z } from 'zod' - -const updateProfileSchema = z.object({ - userId: z.string().uuid(), - name: z.string().min(1).max(100), - email: z.string().email() -}) - -export async function updateProfile(data: unknown) { - // Validate input first - const validated = updateProfileSchema.parse(data) - - // Then authenticate - const session = await verifySession() - if (!session) { - throw new Error('Unauthorized') - } - - // Then authorize - if (session.user.id !== validated.userId) { - throw new Error('Can only update own profile') - } - - // Finally perform the mutation - await db.user.update({ - where: { id: validated.userId }, - data: { - name: validated.name, - email: validated.email - } - }) - - return { success: true } -} -``` - -Reference: [https://nextjs.org/docs/app/guides/authentication](https://nextjs.org/docs/app/guides/authentication) - -### 3.2 Avoid Duplicate Serialization in RSC Props - -**Impact: LOW (reduces network payload by avoiding duplicate serialization)** - -RSC→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. - -**Incorrect: duplicates array** - -```tsx -// RSC: sends 6 strings (2 arrays × 3 items) - -``` - -**Correct: sends 3 strings** - -```tsx -// RSC: send once - - -// Client: transform there -'use client' -const sorted = useMemo(() => [...usernames].sort(), [usernames]) -``` - -**Nested deduplication behavior:** - -```tsx -// string[] - duplicates everything -usernames={['a','b']} sorted={usernames.toSorted()} // sends 4 strings - -// object[] - duplicates array structure only -users={[{id:1},{id:2}]} sorted={users.toSorted()} // sends 2 arrays + 2 unique objects (not 4) -``` - -Deduplication works recursively. Impact varies by data type: - -- `string[]`, `number[]`, `boolean[]`: **HIGH impact** - array + all primitives fully duplicated - -- `object[]`: **LOW impact** - array duplicated, but nested objects deduplicated by reference - -**Operations breaking deduplication: create new references** - -- Arrays: `.toSorted()`, `.filter()`, `.map()`, `.slice()`, `[...arr]` - -- Objects: `{...obj}`, `Object.assign()`, `structuredClone()`, `JSON.parse(JSON.stringify())` - -**More examples:** - -```tsx -// ❌ Bad - u.active)} /> - - -// ✅ Good - - -// Do filtering/destructuring in client -``` - -**Exception:** Pass derived data when transformation is expensive or client doesn't need original. - -### 3.3 Cross-Request LRU Caching - -**Impact: HIGH (caches across requests)** - -`React.cache()` only works within one request. For data shared across sequential requests (user clicks button A then button B), use an LRU cache. - -**Implementation:** - -```typescript -import { LRUCache } from 'lru-cache' - -const cache = new LRUCache({ - max: 1000, - ttl: 5 * 60 * 1000 // 5 minutes -}) - -export async function getUser(id: string) { - const cached = cache.get(id) - if (cached) return cached - - const user = await db.user.findUnique({ where: { id } }) - cache.set(id, user) - return user -} - -// Request 1: DB query, result cached -// Request 2: cache hit, no DB query -``` - -Use when sequential user actions hit multiple endpoints needing the same data within seconds. - -**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. - -**In traditional serverless:** Each invocation runs in isolation, so consider Redis for cross-process caching. - -Reference: [https://github.com/isaacs/node-lru-cache](https://github.com/isaacs/node-lru-cache) - -### 3.4 Minimize Serialization at RSC Boundaries - -**Impact: HIGH (reduces data transfer size)** - -The 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. - -**Incorrect: serializes all 50 fields** - -```tsx -async function Page() { - const user = await fetchUser() // 50 fields - return -} - -'use client' -function Profile({ user }: { user: User }) { - return
{user.name}
// uses 1 field -} -``` - -**Correct: serializes only 1 field** - -```tsx -async function Page() { - const user = await fetchUser() - return -} - -'use client' -function Profile({ name }: { name: string }) { - return
{name}
-} -``` - -### 3.5 Parallel Data Fetching with Component Composition - -**Impact: CRITICAL (eliminates server-side waterfalls)** - -React Server Components execute sequentially within a tree. Restructure with composition to parallelize data fetching. - -**Incorrect: Sidebar waits for Page's fetch to complete** - -```tsx -export default async function Page() { - const header = await fetchHeader() - return ( -
-
{header}
- -
- ) -} - -async function Sidebar() { - const items = await fetchSidebarItems() - return -} -``` - -**Correct: both fetch simultaneously** - -```tsx -async function Header() { - const data = await fetchHeader() - return
{data}
-} - -async function Sidebar() { - const items = await fetchSidebarItems() - return -} - -export default function Page() { - return ( -
-
- -
- ) -} -``` - -**Alternative with children prop:** - -```tsx -async function Header() { - const data = await fetchHeader() - return
{data}
-} - -async function Sidebar() { - const items = await fetchSidebarItems() - return -} - -function Layout({ children }: { children: ReactNode }) { - return ( -
-
- {children} -
- ) -} - -export default function Page() { - return ( - - - - ) -} -``` - -### 3.6 Per-Request Deduplication with React.cache() - -**Impact: MEDIUM (deduplicates within request)** - -Use `React.cache()` for server-side request deduplication. Authentication and database queries benefit most. - -**Usage:** - -```typescript -import { cache } from 'react' - -export const getCurrentUser = cache(async () => { - const session = await auth() - if (!session?.user?.id) return null - return await db.user.findUnique({ - where: { id: session.user.id } - }) -}) -``` - -Within a single request, multiple calls to `getCurrentUser()` execute the query only once. - -**Avoid inline objects as arguments:** - -`React.cache()` uses shallow equality (`Object.is`) to determine cache hits. Inline objects create new references each call, preventing cache hits. - -**Incorrect: always cache miss** - -```typescript -const getUser = cache(async (params: { uid: number }) => { - return await db.user.findUnique({ where: { id: params.uid } }) -}) - -// Each call creates new object, never hits cache -getUser({ uid: 1 }) -getUser({ uid: 1 }) // Cache miss, runs query again -``` - -**Correct: cache hit** - -```typescript -const params = { uid: 1 } -getUser(params) // Query runs -getUser(params) // Cache hit (same reference) -``` - -If you must pass objects, pass the same reference: - -**Next.js-Specific Note:** - -In 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: - -- Database queries (Prisma, Drizzle, etc.) - -- Heavy computations - -- Authentication checks - -- File system operations - -- Any non-fetch async work - -Use `React.cache()` to deduplicate these operations across your component tree. - -Reference: [https://react.dev/reference/react/cache](https://react.dev/reference/react/cache) - -### 3.7 Use after() for Non-Blocking Operations - -**Impact: MEDIUM (faster response times)** - -Use 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. - -**Incorrect: blocks response** - -```tsx -import { logUserAction } from '@/app/utils' - -export async function POST(request: Request) { - // Perform mutation - await updateDatabase(request) - - // Logging blocks the response - const userAgent = request.headers.get('user-agent') || 'unknown' - await logUserAction({ userAgent }) - - return new Response(JSON.stringify({ status: 'success' }), { - status: 200, - headers: { 'Content-Type': 'application/json' } - }) -} -``` - -**Correct: non-blocking** - -```tsx -import { after } from 'next/server' -import { headers, cookies } from 'next/headers' -import { logUserAction } from '@/app/utils' - -export async function POST(request: Request) { - // Perform mutation - await updateDatabase(request) - - // Log after response is sent - after(async () => { - const userAgent = (await headers()).get('user-agent') || 'unknown' - const sessionCookie = (await cookies()).get('session-id')?.value || 'anonymous' - - logUserAction({ sessionCookie, userAgent }) - }) - - return new Response(JSON.stringify({ status: 'success' }), { - status: 200, - headers: { 'Content-Type': 'application/json' } - }) -} -``` - -The response is sent immediately while logging happens in the background. - -**Common use cases:** - -- Analytics tracking - -- Audit logging - -- Sending notifications - -- Cache invalidation - -- Cleanup tasks - -**Important notes:** - -- `after()` runs even if the response fails or redirects - -- Works in Server Actions, Route Handlers, and Server Components - -Reference: [https://nextjs.org/docs/app/api-reference/functions/after](https://nextjs.org/docs/app/api-reference/functions/after) - ---- - -## 4. Client-Side Data Fetching - -**Impact: MEDIUM-HIGH** - -Automatic deduplication and efficient data fetching patterns reduce redundant network requests. - -### 4.1 Deduplicate Global Event Listeners - -**Impact: LOW (single listener for N components)** - -Use `useSWRSubscription()` to share global event listeners across component instances. - -**Incorrect: N instances = N listeners** - -```tsx -function useKeyboardShortcut(key: string, callback: () => void) { - useEffect(() => { - const handler = (e: KeyboardEvent) => { - if (e.metaKey && e.key === key) { - callback() - } - } - window.addEventListener('keydown', handler) - return () => window.removeEventListener('keydown', handler) - }, [key, callback]) -} -``` - -When using the `useKeyboardShortcut` hook multiple times, each instance will register a new listener. - -**Correct: N instances = 1 listener** - -```tsx -import useSWRSubscription from 'swr/subscription' - -// Module-level Map to track callbacks per key -const keyCallbacks = new Map void>>() - -function useKeyboardShortcut(key: string, callback: () => void) { - // Register this callback in the Map - useEffect(() => { - if (!keyCallbacks.has(key)) { - keyCallbacks.set(key, new Set()) - } - keyCallbacks.get(key)!.add(callback) - - return () => { - const set = keyCallbacks.get(key) - if (set) { - set.delete(callback) - if (set.size === 0) { - keyCallbacks.delete(key) - } - } - } - }, [key, callback]) - - useSWRSubscription('global-keydown', () => { - const handler = (e: KeyboardEvent) => { - if (e.metaKey && keyCallbacks.has(e.key)) { - keyCallbacks.get(e.key)!.forEach(cb => cb()) - } - } - window.addEventListener('keydown', handler) - return () => window.removeEventListener('keydown', handler) - }) -} - -function Profile() { - // Multiple shortcuts will share the same listener - useKeyboardShortcut('p', () => { /* ... */ }) - useKeyboardShortcut('k', () => { /* ... */ }) - // ... -} -``` - -### 4.2 Use Passive Event Listeners for Scrolling Performance - -**Impact: MEDIUM (eliminates scroll delay caused by event listeners)** - -Add `{ 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. - -**Incorrect:** - -```typescript -useEffect(() => { - const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX) - const handleWheel = (e: WheelEvent) => console.log(e.deltaY) - - document.addEventListener('touchstart', handleTouch) - document.addEventListener('wheel', handleWheel) - - return () => { - document.removeEventListener('touchstart', handleTouch) - document.removeEventListener('wheel', handleWheel) - } -}, []) -``` - -**Correct:** - -```typescript -useEffect(() => { - const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX) - const handleWheel = (e: WheelEvent) => console.log(e.deltaY) - - document.addEventListener('touchstart', handleTouch, { passive: true }) - document.addEventListener('wheel', handleWheel, { passive: true }) - - return () => { - document.removeEventListener('touchstart', handleTouch) - document.removeEventListener('wheel', handleWheel) - } -}, []) -``` - -**Use passive when:** tracking/analytics, logging, any listener that doesn't call `preventDefault()`. - -**Don't use passive when:** implementing custom swipe gestures, custom zoom controls, or any listener that needs `preventDefault()`. - -### 4.3 Use SWR for Automatic Deduplication - -**Impact: MEDIUM-HIGH (automatic deduplication)** - -SWR enables request deduplication, caching, and revalidation across component instances. - -**Incorrect: no deduplication, each instance fetches** - -```tsx -function UserList() { - const [users, setUsers] = useState([]) - useEffect(() => { - fetch('/api/users') - .then(r => r.json()) - .then(setUsers) - }, []) -} -``` - -**Correct: multiple instances share one request** - -```tsx -import useSWR from 'swr' - -function UserList() { - const { data: users } = useSWR('/api/users', fetcher) -} -``` - -**For immutable data:** - -```tsx -import { useImmutableSWR } from '@/lib/swr' - -function StaticContent() { - const { data } = useImmutableSWR('/api/config', fetcher) -} -``` - -**For mutations:** - -```tsx -import { useSWRMutation } from 'swr/mutation' - -function UpdateButton() { - const { trigger } = useSWRMutation('/api/user', updateUser) - return -} -``` - -Reference: [https://swr.vercel.app](https://swr.vercel.app) - -### 4.4 Version and Minimize localStorage Data - -**Impact: MEDIUM (prevents schema conflicts, reduces storage size)** - -Add version prefix to keys and store only needed fields. Prevents schema conflicts and accidental storage of sensitive data. - -**Incorrect:** - -```typescript -// No version, stores everything, no error handling -localStorage.setItem('userConfig', JSON.stringify(fullUserObject)) -const data = localStorage.getItem('userConfig') -``` - -**Correct:** - -```typescript -const VERSION = 'v2' - -function saveConfig(config: { theme: string; language: string }) { - try { - localStorage.setItem(`userConfig:${VERSION}`, JSON.stringify(config)) - } catch { - // Throws in incognito/private browsing, quota exceeded, or disabled - } -} - -function loadConfig() { - try { - const data = localStorage.getItem(`userConfig:${VERSION}`) - return data ? JSON.parse(data) : null - } catch { - return null - } -} - -// Migration from v1 to v2 -function migrate() { - try { - const v1 = localStorage.getItem('userConfig:v1') - if (v1) { - const old = JSON.parse(v1) - saveConfig({ theme: old.darkMode ? 'dark' : 'light', language: old.lang }) - localStorage.removeItem('userConfig:v1') - } - } catch {} -} -``` - -**Store minimal fields from server responses:** - -```typescript -// User object has 20+ fields, only store what UI needs -function cachePrefs(user: FullUser) { - try { - localStorage.setItem('prefs:v1', JSON.stringify({ - theme: user.preferences.theme, - notifications: user.preferences.notifications - })) - } catch {} -} -``` - -**Always wrap in try-catch:** `getItem()` and `setItem()` throw in incognito/private browsing (Safari, Firefox), when quota exceeded, or when disabled. - -**Benefits:** Schema evolution via versioning, reduced storage size, prevents storing tokens/PII/internal flags. - ---- - -## 5. Re-render Optimization - -**Impact: MEDIUM** - -Reducing unnecessary re-renders minimizes wasted computation and improves UI responsiveness. - -### 5.1 Calculate Derived State During Rendering - -**Impact: MEDIUM (avoids redundant renders and state drift)** - -If 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. - -**Incorrect: redundant state and effect** - -```tsx -function Form() { - const [firstName, setFirstName] = useState('First') - const [lastName, setLastName] = useState('Last') - const [fullName, setFullName] = useState('') - - useEffect(() => { - setFullName(firstName + ' ' + lastName) - }, [firstName, lastName]) - - return

{fullName}

-} -``` - -**Correct: derive during render** - -```tsx -function Form() { - const [firstName, setFirstName] = useState('First') - const [lastName, setLastName] = useState('Last') - const fullName = firstName + ' ' + lastName - - return

{fullName}

-} -``` - -Reference: [https://react.dev/learn/you-might-not-need-an-effect](https://react.dev/learn/you-might-not-need-an-effect) - -### 5.2 Defer State Reads to Usage Point - -**Impact: MEDIUM (avoids unnecessary subscriptions)** - -Don't subscribe to dynamic state (searchParams, localStorage) if you only read it inside callbacks. - -**Incorrect: subscribes to all searchParams changes** - -```tsx -function ShareButton({ chatId }: { chatId: string }) { - const searchParams = useSearchParams() - - const handleShare = () => { - const ref = searchParams.get('ref') - shareChat(chatId, { ref }) - } - - return -} -``` - -**Correct: reads on demand, no subscription** - -```tsx -function ShareButton({ chatId }: { chatId: string }) { - const handleShare = () => { - const params = new URLSearchParams(window.location.search) - const ref = params.get('ref') - shareChat(chatId, { ref }) - } - - return -} -``` - -### 5.3 Do not wrap a simple expression with a primitive result type in useMemo - -**Impact: LOW-MEDIUM (wasted computation on every render)** - -When an expression is simple (few logical or arithmetical operators) and has a primitive result type (boolean, number, string), do not wrap it in `useMemo`. - -Calling `useMemo` and comparing hook dependencies may consume more resources than the expression itself. - -**Incorrect:** - -```tsx -function Header({ user, notifications }: Props) { - const isLoading = useMemo(() => { - return user.isLoading || notifications.isLoading - }, [user.isLoading, notifications.isLoading]) - - if (isLoading) return - // return some markup -} -``` - -**Correct:** - -```tsx -function Header({ user, notifications }: Props) { - const isLoading = user.isLoading || notifications.isLoading - - if (isLoading) return - // return some markup -} -``` - -### 5.4 Extract Default Non-primitive Parameter Value from Memoized Component to Constant - -**Impact: MEDIUM (restores memoization by using a constant for default value)** - -When 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()`. - -To address this issue, extract the default value into a constant. - -**Incorrect: `onClick` has different values on every rerender** - -```tsx -const UserAvatar = memo(function UserAvatar({ onClick = () => {} }: { onClick?: () => void }) { - // ... -}) - -// Used without optional onClick - -``` - -**Correct: stable default value** - -```tsx -const NOOP = () => {}; - -const UserAvatar = memo(function UserAvatar({ onClick = NOOP }: { onClick?: () => void }) { - // ... -}) - -// Used without optional onClick - -``` - -### 5.5 Extract to Memoized Components - -**Impact: MEDIUM (enables early returns)** - -Extract expensive work into memoized components to enable early returns before computation. - -**Incorrect: computes avatar even when loading** - -```tsx -function Profile({ user, loading }: Props) { - const avatar = useMemo(() => { - const id = computeAvatarId(user) - return - }, [user]) - - if (loading) return - return
{avatar}
-} -``` - -**Correct: skips computation when loading** - -```tsx -const UserAvatar = memo(function UserAvatar({ user }: { user: User }) { - const id = useMemo(() => computeAvatarId(user), [user]) - return -}) - -function Profile({ user, loading }: Props) { - if (loading) return - return ( -
- -
- ) -} -``` - -**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. - -### 5.6 Narrow Effect Dependencies - -**Impact: LOW (minimizes effect re-runs)** - -Specify primitive dependencies instead of objects to minimize effect re-runs. - -**Incorrect: re-runs on any user field change** - -```tsx -useEffect(() => { - console.log(user.id) -}, [user]) -``` - -**Correct: re-runs only when id changes** - -```tsx -useEffect(() => { - console.log(user.id) -}, [user.id]) -``` - -**For derived state, compute outside effect:** - -```tsx -// Incorrect: runs on width=767, 766, 765... -useEffect(() => { - if (width < 768) { - enableMobileMode() - } -}, [width]) - -// Correct: runs only on boolean transition -const isMobile = width < 768 -useEffect(() => { - if (isMobile) { - enableMobileMode() - } -}, [isMobile]) -``` - -### 5.7 Put Interaction Logic in Event Handlers - -**Impact: MEDIUM (avoids effect re-runs and duplicate side effects)** - -If 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. - -**Incorrect: event modeled as state + effect** - -```tsx -function Form() { - const [submitted, setSubmitted] = useState(false) - const theme = useContext(ThemeContext) - - useEffect(() => { - if (submitted) { - post('/api/register') - showToast('Registered', theme) - } - }, [submitted, theme]) - - return -} -``` - -**Correct: do it in the handler** - -```tsx -function Form() { - const theme = useContext(ThemeContext) - - function handleSubmit() { - post('/api/register') - showToast('Registered', theme) - } - - return -} -``` - -Reference: [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) - -### 5.8 Subscribe to Derived State - -**Impact: MEDIUM (reduces re-render frequency)** - -Subscribe to derived boolean state instead of continuous values to reduce re-render frequency. - -**Incorrect: re-renders on every pixel change** - -```tsx -function Sidebar() { - const width = useWindowWidth() // updates continuously - const isMobile = width < 768 - return