Explorar o código

Neo: rewrite agent docs for the UNA repo, drop generic skills (#5789)

NEO moved into the UNA repo as neo/, and its agent docs still described
the old standalone monorepo. An audit of every claim against the code
found much of the guidance wrong for this repo, so the docs were rewritten
from the code.

- neo/AGENTS.md (was agents.md): upstream vs fork rules, the PR workflow,
  every customization seam and how forks override it, and only
  project-specific conventions. Removes generic React/Next/TS advice and
  wrong guidance ('use cache' with cacheComponents off, invented auth,
  Pusher and component examples, nonexistent files).
- neo/README.md (was readme.md): setup, env variables, commands and
  deployment; drops the stale customization and branch sections.
- neo/CLAUDE.md (was claude.md): imports AGENTS.md and the fork's own
  notes. Uppercase names load on case-sensitive file systems too.
- Deletes the 13 generic third-party skills, skills-lock.json,
  .cursorrules, .cursor/rules, uniwind.md (migration finished),
  design/README.md (a copy of uniwind.md) and the empty info.txt files.
- una-api skill: fixes the response shape, block visibility constants
  and fetcher claims.
- customization/resources: rewritten README; fork note templates changed
  only above the "Per-project additions" marker so forks' notes merge
  cleanly.
- .easignore comments translated to English; animated-icons.md fixes.
- UNA root: NEO section in AGENTS.md, CLAUDE.md importing AGENTS.md
  (excluded from the release package like AGENTS.md).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Andrey Yasko hai 1 semana
pai
achega
6ac00909c8
Modificáronse 100 ficheiros con 60 adicións e 24727 borrados
  1. 10 0
      AGENTS.md
  2. 5 0
      CLAUDE.md
  3. 1 0
      build.xml
  4. 0 715
      neo/.agents/skills/agent-browser/SKILL.md
  5. 0 303
      neo/.agents/skills/agent-browser/references/authentication.md
  6. 0 292
      neo/.agents/skills/agent-browser/references/commands.md
  7. 0 120
      neo/.agents/skills/agent-browser/references/profiling.md
  8. 0 194
      neo/.agents/skills/agent-browser/references/proxy-support.md
  9. 0 193
      neo/.agents/skills/agent-browser/references/session-management.md
  10. 0 219
      neo/.agents/skills/agent-browser/references/snapshot-refs.md
  11. 0 173
      neo/.agents/skills/agent-browser/references/video-recording.md
  12. 0 105
      neo/.agents/skills/agent-browser/templates/authenticated-session.sh
  13. 0 69
      neo/.agents/skills/agent-browser/templates/capture-workflow.sh
  14. 0 62
      neo/.agents/skills/agent-browser/templates/form-automation.sh
  15. 0 321
      neo/.agents/skills/building-native-ui/SKILL.md
  16. 0 220
      neo/.agents/skills/building-native-ui/references/animations.md
  17. 0 270
      neo/.agents/skills/building-native-ui/references/controls.md
  18. 0 253
      neo/.agents/skills/building-native-ui/references/form-sheet.md
  19. 0 106
      neo/.agents/skills/building-native-ui/references/gradients.md
  20. 0 213
      neo/.agents/skills/building-native-ui/references/icons.md
  21. 0 198
      neo/.agents/skills/building-native-ui/references/media.md
  22. 0 229
      neo/.agents/skills/building-native-ui/references/route-structure.md
  23. 0 248
      neo/.agents/skills/building-native-ui/references/search.md
  24. 0 121
      neo/.agents/skills/building-native-ui/references/storage.md
  25. 0 433
      neo/.agents/skills/building-native-ui/references/tabs.md
  26. 0 284
      neo/.agents/skills/building-native-ui/references/toolbar-and-headers.md
  27. 0 197
      neo/.agents/skills/building-native-ui/references/visual-effects.md
  28. 0 605
      neo/.agents/skills/building-native-ui/references/webgpu-three.md
  29. 0 158
      neo/.agents/skills/building-native-ui/references/zoom-transitions.md
  30. 0 296
      neo/.agents/skills/deploy-to-vercel/SKILL.md
  31. 0 301
      neo/.agents/skills/deploy-to-vercel/resources/deploy-codex.sh
  32. 0 301
      neo/.agents/skills/deploy-to-vercel/resources/deploy.sh
  33. 0 153
      neo/.agents/skills/next-best-practices/SKILL.md
  34. 0 87
      neo/.agents/skills/next-best-practices/async-patterns.md
  35. 0 180
      neo/.agents/skills/next-best-practices/bundling.md
  36. 0 297
      neo/.agents/skills/next-best-practices/data-patterns.md
  37. 0 105
      neo/.agents/skills/next-best-practices/debug-tricks.md
  38. 0 73
      neo/.agents/skills/next-best-practices/directives.md
  39. 0 227
      neo/.agents/skills/next-best-practices/error-handling.md
  40. 0 140
      neo/.agents/skills/next-best-practices/file-conventions.md
  41. 0 245
      neo/.agents/skills/next-best-practices/font.md
  42. 0 108
      neo/.agents/skills/next-best-practices/functions.md
  43. 0 91
      neo/.agents/skills/next-best-practices/hydration-error.md
  44. 0 173
      neo/.agents/skills/next-best-practices/image.md
  45. 0 301
      neo/.agents/skills/next-best-practices/metadata.md
  46. 0 287
      neo/.agents/skills/next-best-practices/parallel-routes.md
  47. 0 146
      neo/.agents/skills/next-best-practices/route-handlers.md
  48. 0 159
      neo/.agents/skills/next-best-practices/rsc-boundaries.md
  49. 0 39
      neo/.agents/skills/next-best-practices/runtime-selection.md
  50. 0 141
      neo/.agents/skills/next-best-practices/scripts.md
  51. 0 371
      neo/.agents/skills/next-best-practices/self-hosting.md
  52. 0 67
      neo/.agents/skills/next-best-practices/suspense-boundaries.md
  53. 0 411
      neo/.agents/skills/next-cache-components/SKILL.md
  54. 0 50
      neo/.agents/skills/next-upgrade/SKILL.md
  55. 0 32
      neo/.agents/skills/react-doctor/SKILL.md
  56. 0 951
      neo/.agents/skills/turborepo/SKILL.md
  57. 0 70
      neo/.agents/skills/turborepo/command/turborepo.md
  58. 0 241
      neo/.agents/skills/turborepo/references/best-practices/RULE.md
  59. 0 246
      neo/.agents/skills/turborepo/references/best-practices/dependencies.md
  60. 0 335
      neo/.agents/skills/turborepo/references/best-practices/packages.md
  61. 0 297
      neo/.agents/skills/turborepo/references/best-practices/structure.md
  62. 0 126
      neo/.agents/skills/turborepo/references/boundaries/RULE.md
  63. 0 153
      neo/.agents/skills/turborepo/references/caching/RULE.md
  64. 0 190
      neo/.agents/skills/turborepo/references/caching/gotchas.md
  65. 0 127
      neo/.agents/skills/turborepo/references/caching/remote-cache.md
  66. 0 79
      neo/.agents/skills/turborepo/references/ci/RULE.md
  67. 0 162
      neo/.agents/skills/turborepo/references/ci/github-actions.md
  68. 0 145
      neo/.agents/skills/turborepo/references/ci/patterns.md
  69. 0 103
      neo/.agents/skills/turborepo/references/ci/vercel.md
  70. 0 100
      neo/.agents/skills/turborepo/references/cli/RULE.md
  71. 0 297
      neo/.agents/skills/turborepo/references/cli/commands.md
  72. 0 235
      neo/.agents/skills/turborepo/references/configuration/RULE.md
  73. 0 239
      neo/.agents/skills/turborepo/references/configuration/global-options.md
  74. 0 368
      neo/.agents/skills/turborepo/references/configuration/gotchas.md
  75. 0 325
      neo/.agents/skills/turborepo/references/configuration/tasks.md
  76. 0 123
      neo/.agents/skills/turborepo/references/environment/RULE.md
  77. 0 175
      neo/.agents/skills/turborepo/references/environment/gotchas.md
  78. 0 101
      neo/.agents/skills/turborepo/references/environment/modes.md
  79. 0 148
      neo/.agents/skills/turborepo/references/filtering/RULE.md
  80. 0 152
      neo/.agents/skills/turborepo/references/filtering/patterns.md
  81. 0 99
      neo/.agents/skills/turborepo/references/watch/RULE.md
  82. 44 102
      neo/.agents/skills/una-api/SKILL.md
  83. 0 2122
      neo/.agents/skills/uniwind/SKILL.md
  84. 0 946
      neo/.agents/skills/vercel-composition-patterns/AGENTS.md
  85. 0 60
      neo/.agents/skills/vercel-composition-patterns/README.md
  86. 0 89
      neo/.agents/skills/vercel-composition-patterns/SKILL.md
  87. 0 29
      neo/.agents/skills/vercel-composition-patterns/rules/_sections.md
  88. 0 24
      neo/.agents/skills/vercel-composition-patterns/rules/_template.md
  89. 0 100
      neo/.agents/skills/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md
  90. 0 112
      neo/.agents/skills/vercel-composition-patterns/rules/architecture-compound-components.md
  91. 0 87
      neo/.agents/skills/vercel-composition-patterns/rules/patterns-children-over-render-props.md
  92. 0 100
      neo/.agents/skills/vercel-composition-patterns/rules/patterns-explicit-variants.md
  93. 0 42
      neo/.agents/skills/vercel-composition-patterns/rules/react19-no-forwardref.md
  94. 0 191
      neo/.agents/skills/vercel-composition-patterns/rules/state-context-interface.md
  95. 0 113
      neo/.agents/skills/vercel-composition-patterns/rules/state-decouple-implementation.md
  96. 0 125
      neo/.agents/skills/vercel-composition-patterns/rules/state-lift-state.md
  97. 0 3502
      neo/.agents/skills/vercel-react-best-practices/AGENTS.md
  98. 0 123
      neo/.agents/skills/vercel-react-best-practices/README.md
  99. 0 145
      neo/.agents/skills/vercel-react-best-practices/SKILL.md
  100. 0 46
      neo/.agents/skills/vercel-react-best-practices/rules/_sections.md

+ 10 - 0
AGENTS.md

@@ -1,5 +1,15 @@
 # Notes for coding agents
 
+## NEO (`neo/`)
+
+`neo/` is NEO, the Next.js and Expo client for UNA, kept here as a git subtree. Before changing anything under `neo/`, read [`neo/AGENTS.md`](neo/AGENTS.md). Claude Code loads it through `neo/CLAUDE.md` when it opens a file there.
+
+- Run NEO's yarn scripts from `neo/`, not from the UNA root.
+- Branch from `master` and open the PR here. Start the title of a NEO-only PR with `Neo:`.
+- Never commit to unacms/neo. It is a mirror that `.github/workflows/sync-neo.yml` pushes on every push to `master`, and client projects fork it.
+- UNA CI doesn't check `neo/`. Run `yarn typecheck` and `yarn lint` in `neo/` before you push.
+- A UNA service that NEO calls must return JSON for guests too; see [`neo/.agents/skills/una-api/SKILL.md`](neo/.agents/skills/una-api/SKILL.md).
+
 ## Studio app icons (`studio/template/images/icons/wi-*.svg`, module `template/images/icons/std-icon.svg`)
 
 Every launcher tile is one SVG with three layers, in this order:

+ 5 - 0
CLAUDE.md

@@ -0,0 +1,5 @@
+<!--
+Claude Code reads AGENTS.md by itself only from v2.1.277, and only when no
+CLAUDE.md exists, so this file has to import it.
+-->
+@AGENTS.md

+ 1 - 0
build.xml

@@ -62,6 +62,7 @@
         <exclude name="ai/**" />
         <exclude name="inc/header*" />
         <exclude name="AGENTS.md" />
+        <exclude name="CLAUDE.md" />
         <exclude name="build.xml" />
         <exclude name="build.properties" />
         <exclude name="bower.json" />

+ 0 - 715
neo/.agents/skills/agent-browser/SKILL.md

@@ -1,715 +0,0 @@
----
-name: agent-browser
-description: Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction.
-allowed-tools: Bash(npx agent-browser:*), Bash(agent-browser:*)
----
-
-# Browser Automation with agent-browser
-
-The CLI uses Chrome/Chromium via CDP directly. Install via `npm i -g agent-browser`, `brew install agent-browser`, or `cargo install agent-browser`. Run `agent-browser install` to download Chrome. Run `agent-browser upgrade` to update to the latest version.
-
-## Core Workflow
-
-Every browser automation follows this pattern:
-
-1. **Navigate**: `agent-browser open <url>`
-2. **Snapshot**: `agent-browser snapshot -i` (get element refs like `@e1`, `@e2`)
-3. **Interact**: Use refs to click, fill, select
-4. **Re-snapshot**: After navigation or DOM changes, get fresh refs
-
-```bash
-agent-browser open https://example.com/form
-agent-browser snapshot -i
-# Output: @e1 [input type="email"], @e2 [input type="password"], @e3 [button] "Submit"
-
-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
-```
-
-## Command Chaining
-
-Commands can be chained with `&&` in a single shell invocation. The browser persists between commands via a background daemon, so chaining is safe and more efficient than separate calls.
-
-```bash
-# Chain open + wait + snapshot in one call
-agent-browser open https://example.com && agent-browser wait --load networkidle && agent-browser snapshot -i
-
-# Chain multiple interactions
-agent-browser fill @e1 "user@example.com" && agent-browser fill @e2 "password123" && agent-browser click @e3
-
-# Navigate and capture
-agent-browser open https://example.com && agent-browser wait --load networkidle && agent-browser screenshot page.png
-```
-
-**When to chain:** Use `&&` when you don't need to read the output of an intermediate command before proceeding (e.g., open + wait + screenshot). Run commands separately when you need to parse the output first (e.g., snapshot to discover refs, then interact using those refs).
-
-## Handling Authentication
-
-When automating a site that requires login, choose the approach that fits:
-
-**Option 1: Import auth from the user's browser (fastest for one-off tasks)**
-
-```bash
-# Connect to the user's running Chrome (they're already logged in)
-agent-browser --auto-connect state save ./auth.json
-# Use that auth state
-agent-browser --state ./auth.json open https://app.example.com/dashboard
-```
-
-State files contain session tokens in plaintext -- add to `.gitignore` and delete when no longer needed. Set `AGENT_BROWSER_ENCRYPTION_KEY` for encryption at rest.
-
-**Option 2: Persistent profile (simplest for recurring tasks)**
-
-```bash
-# First run: login manually or via automation
-agent-browser --profile ~/.myapp open https://app.example.com/login
-# ... fill credentials, submit ...
-
-# All future runs: already authenticated
-agent-browser --profile ~/.myapp open https://app.example.com/dashboard
-```
-
-**Option 3: Session name (auto-save/restore cookies + localStorage)**
-
-```bash
-agent-browser --session-name myapp open https://app.example.com/login
-# ... login flow ...
-agent-browser close  # State auto-saved
-
-# Next time: state auto-restored
-agent-browser --session-name myapp open https://app.example.com/dashboard
-```
-
-**Option 4: Auth vault (credentials stored encrypted, login by name)**
-
-```bash
-echo "$PASSWORD" | agent-browser auth save myapp --url https://app.example.com/login --username user --password-stdin
-agent-browser auth login myapp
-```
-
-`auth login` navigates with `load` and then waits for login form selectors to appear before filling/clicking, which is more reliable on delayed SPA login screens.
-
-**Option 5: State file (manual save/load)**
-
-```bash
-# After logging in:
-agent-browser state save ./auth.json
-# In a future session:
-agent-browser state load ./auth.json
-agent-browser open https://app.example.com/dashboard
-```
-
-See [references/authentication.md](references/authentication.md) for OAuth, 2FA, cookie-based auth, and token refresh patterns.
-
-## Essential Commands
-
-```bash
-# Navigation
-agent-browser open <url>              # Navigate (aliases: goto, navigate)
-agent-browser close                   # Close browser
-
-# Snapshot
-agent-browser snapshot -i             # Interactive elements with refs (recommended)
-agent-browser snapshot -s "#selector" # Scope to CSS selector
-
-# Interaction (use @refs from snapshot)
-agent-browser click @e1               # Click element
-agent-browser click @e1 --new-tab     # Click and open in new tab
-agent-browser fill @e2 "text"         # Clear and type text
-agent-browser type @e2 "text"         # Type without clearing
-agent-browser select @e1 "option"     # Select dropdown option
-agent-browser check @e1               # Check checkbox
-agent-browser press Enter             # Press key
-agent-browser keyboard type "text"    # Type at current focus (no selector)
-agent-browser keyboard inserttext "text"  # Insert without key events
-agent-browser scroll down 500         # Scroll page
-agent-browser scroll down 500 --selector "div.content"  # Scroll within a specific container
-
-# Get information
-agent-browser get text @e1            # Get element text
-agent-browser get url                 # Get current URL
-agent-browser get title               # Get page title
-agent-browser get cdp-url             # Get CDP WebSocket URL
-
-# Wait
-agent-browser wait @e1                # Wait for element
-agent-browser wait --load networkidle # Wait for network idle
-agent-browser wait --url "**/page"    # Wait for URL pattern
-agent-browser wait 2000               # Wait milliseconds
-agent-browser wait --text "Welcome"    # Wait for text to appear (substring match)
-agent-browser wait --fn "!document.body.innerText.includes('Loading...')"  # Wait for text to disappear
-agent-browser wait "#spinner" --state hidden  # Wait for element to disappear
-
-# Downloads
-agent-browser download @e1 ./file.pdf          # Click element to trigger download
-agent-browser wait --download ./output.zip     # Wait for any download to complete
-agent-browser --download-path ./downloads open <url>  # Set default download directory
-
-# Network
-agent-browser network requests                 # Inspect tracked requests
-agent-browser network requests --type xhr,fetch  # Filter by resource type
-agent-browser network requests --method POST   # Filter by HTTP method
-agent-browser network requests --status 2xx    # Filter by status (200, 2xx, 400-499)
-agent-browser network request <requestId>      # View full request/response detail
-agent-browser network route "**/api/*" --abort  # Block matching requests
-agent-browser network har start                # Start HAR recording
-agent-browser network har stop ./capture.har   # Stop and save HAR file
-
-# Viewport & Device Emulation
-agent-browser set viewport 1920 1080          # Set viewport size (default: 1280x720)
-agent-browser set viewport 1920 1080 2        # 2x retina (same CSS size, higher res screenshots)
-agent-browser set device "iPhone 14"          # Emulate device (viewport + user agent)
-
-# Capture
-agent-browser screenshot              # Screenshot to temp dir
-agent-browser screenshot --full       # Full page screenshot
-agent-browser screenshot --annotate   # Annotated screenshot with numbered element labels
-agent-browser screenshot --screenshot-dir ./shots  # Save to custom directory
-agent-browser screenshot --screenshot-format jpeg --screenshot-quality 80
-agent-browser pdf output.pdf          # Save as PDF
-
-# Clipboard
-agent-browser clipboard read                      # Read text from clipboard
-agent-browser clipboard write "Hello, World!"     # Write text to clipboard
-agent-browser clipboard copy                      # Copy current selection
-agent-browser clipboard paste                     # Paste from clipboard
-
-# Dialogs (alert, confirm, prompt)
-agent-browser dialog accept              # Accept dialog
-agent-browser dialog accept "my input"   # Accept prompt dialog with text
-agent-browser dialog dismiss             # Dismiss/cancel dialog
-agent-browser dialog status              # Check if a dialog is currently open
-
-# Diff (compare page states)
-agent-browser diff snapshot                          # Compare current vs last snapshot
-agent-browser diff snapshot --baseline before.txt    # Compare current vs saved file
-agent-browser diff screenshot --baseline before.png  # Visual pixel diff
-agent-browser diff url <url1> <url2>                 # Compare two pages
-agent-browser diff url <url1> <url2> --wait-until networkidle  # Custom wait strategy
-agent-browser diff url <url1> <url2> --selector "#main"  # Scope to element
-```
-
-## Batch Execution
-
-Execute multiple commands in a single invocation by piping a JSON array of string arrays to `batch`. This avoids per-command process startup overhead when running multi-step workflows.
-
-```bash
-echo '[
-  ["open", "https://example.com"],
-  ["snapshot", "-i"],
-  ["click", "@e1"],
-  ["screenshot", "result.png"]
-]' | agent-browser batch --json
-
-# Stop on first error
-agent-browser batch --bail < commands.json
-```
-
-Use `batch` when you have a known sequence of commands that don't depend on intermediate output. Use separate commands or `&&` chaining when you need to parse output between steps (e.g., snapshot to discover refs, then interact).
-
-## Common Patterns
-
-### Form Submission
-
-```bash
-agent-browser open https://example.com/signup
-agent-browser snapshot -i
-agent-browser fill @e1 "Jane Doe"
-agent-browser fill @e2 "jane@example.com"
-agent-browser select @e3 "California"
-agent-browser check @e4
-agent-browser click @e5
-agent-browser wait --load networkidle
-```
-
-### Authentication with Auth Vault (Recommended)
-
-```bash
-# Save credentials once (encrypted with AGENT_BROWSER_ENCRYPTION_KEY)
-# Recommended: pipe password via stdin to avoid shell history exposure
-echo "pass" | agent-browser auth save github --url https://github.com/login --username user --password-stdin
-
-# Login using saved profile (LLM never sees password)
-agent-browser auth login github
-
-# List/show/delete profiles
-agent-browser auth list
-agent-browser auth show github
-agent-browser auth delete github
-```
-
-`auth login` waits for username/password/submit selectors before interacting, with a timeout tied to the default action timeout.
-
-### Authentication with State Persistence
-
-```bash
-# Login once and save state
-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
-
-# Reuse in future sessions
-agent-browser state load auth.json
-agent-browser open https://app.example.com/dashboard
-```
-
-### Session Persistence
-
-```bash
-# Auto-save/restore cookies and localStorage across browser restarts
-agent-browser --session-name myapp open https://app.example.com/login
-# ... login flow ...
-agent-browser close  # State auto-saved to ~/.agent-browser/sessions/
-
-# Next time, state is auto-loaded
-agent-browser --session-name myapp open https://app.example.com/dashboard
-
-# Encrypt state at rest
-export AGENT_BROWSER_ENCRYPTION_KEY=$(openssl rand -hex 32)
-agent-browser --session-name secure open https://app.example.com
-
-# Manage saved states
-agent-browser state list
-agent-browser state show myapp-default.json
-agent-browser state clear myapp
-agent-browser state clean --older-than 7
-```
-
-### Working with Iframes
-
-Iframe content is automatically inlined in snapshots. Refs inside iframes carry frame context, so you can interact with them directly.
-
-```bash
-agent-browser open https://example.com/checkout
-agent-browser snapshot -i
-# @e1 [heading] "Checkout"
-# @e2 [Iframe] "payment-frame"
-#   @e3 [input] "Card number"
-#   @e4 [input] "Expiry"
-#   @e5 [button] "Pay"
-
-# Interact directly — no frame switch needed
-agent-browser fill @e3 "4111111111111111"
-agent-browser fill @e4 "12/28"
-agent-browser click @e5
-
-# To scope a snapshot to one iframe:
-agent-browser frame @e2
-agent-browser snapshot -i         # Only iframe content
-agent-browser frame main          # Return to main frame
-```
-
-### Data Extraction
-
-```bash
-agent-browser open https://example.com/products
-agent-browser snapshot -i
-agent-browser get text @e5           # Get specific element text
-agent-browser get text body > page.txt  # Get all page text
-
-# JSON output for parsing
-agent-browser snapshot -i --json
-agent-browser get text @e1 --json
-```
-
-### Parallel Sessions
-
-```bash
-agent-browser --session site1 open https://site-a.com
-agent-browser --session site2 open https://site-b.com
-
-agent-browser --session site1 snapshot -i
-agent-browser --session site2 snapshot -i
-
-agent-browser session list
-```
-
-### Connect to Existing Chrome
-
-```bash
-# Auto-discover running Chrome with remote debugging enabled
-agent-browser --auto-connect open https://example.com
-agent-browser --auto-connect snapshot
-
-# Or with explicit CDP port
-agent-browser --cdp 9222 snapshot
-```
-
-Auto-connect discovers Chrome via `DevToolsActivePort`, common debugging ports (9222, 9229), and falls back to a direct WebSocket connection if HTTP-based CDP discovery fails.
-
-### Color Scheme (Dark Mode)
-
-```bash
-# Persistent dark mode via flag (applies to all pages and new tabs)
-agent-browser --color-scheme dark open https://example.com
-
-# Or via environment variable
-AGENT_BROWSER_COLOR_SCHEME=dark agent-browser open https://example.com
-
-# Or set during session (persists for subsequent commands)
-agent-browser set media dark
-```
-
-### Viewport & Responsive Testing
-
-```bash
-# Set a custom viewport size (default is 1280x720)
-agent-browser set viewport 1920 1080
-agent-browser screenshot desktop.png
-
-# Test mobile-width layout
-agent-browser set viewport 375 812
-agent-browser screenshot mobile.png
-
-# Retina/HiDPI: same CSS layout at 2x pixel density
-# Screenshots stay at logical viewport size, but content renders at higher DPI
-agent-browser set viewport 1920 1080 2
-agent-browser screenshot retina.png
-
-# Device emulation (sets viewport + user agent in one step)
-agent-browser set device "iPhone 14"
-agent-browser screenshot device.png
-```
-
-The `scale` parameter (3rd argument) sets `window.devicePixelRatio` without changing CSS layout. Use it when testing retina rendering or capturing higher-resolution screenshots.
-
-### Visual Browser (Debugging)
-
-```bash
-agent-browser --headed open https://example.com
-agent-browser highlight @e1          # Highlight element
-agent-browser inspect                # Open Chrome DevTools for the active page
-agent-browser record start demo.webm # Record session
-agent-browser profiler start         # Start Chrome DevTools profiling
-agent-browser profiler stop trace.json # Stop and save profile (path optional)
-```
-
-Use `AGENT_BROWSER_HEADED=1` to enable headed mode via environment variable. Browser extensions work in both headed and headless mode.
-
-### Local Files (PDFs, HTML)
-
-```bash
-# Open local files with file:// URLs
-agent-browser --allow-file-access open file:///path/to/document.pdf
-agent-browser --allow-file-access open file:///path/to/page.html
-agent-browser screenshot output.png
-```
-
-### iOS Simulator (Mobile Safari)
-
-```bash
-# List available iOS simulators
-agent-browser device list
-
-# Launch Safari on a specific device
-agent-browser -p ios --device "iPhone 16 Pro" open https://example.com
-
-# Same workflow as desktop - snapshot, interact, re-snapshot
-agent-browser -p ios snapshot -i
-agent-browser -p ios tap @e1          # Tap (alias for click)
-agent-browser -p ios fill @e2 "text"
-agent-browser -p ios swipe up         # Mobile-specific gesture
-
-# Take screenshot
-agent-browser -p ios screenshot mobile.png
-
-# Close session (shuts down simulator)
-agent-browser -p ios close
-```
-
-**Requirements:** macOS with Xcode, Appium (`npm install -g appium && appium driver install xcuitest`)
-
-**Real devices:** Works with physical iOS devices if pre-configured. Use `--device "<UDID>"` where UDID is from `xcrun xctrace list devices`.
-
-## Security
-
-All security features are opt-in. By default, agent-browser imposes no restrictions on navigation, actions, or output.
-
-### Content Boundaries (Recommended for AI Agents)
-
-Enable `--content-boundaries` to wrap page-sourced output in markers that help LLMs distinguish tool output from untrusted page content:
-
-```bash
-export AGENT_BROWSER_CONTENT_BOUNDARIES=1
-agent-browser snapshot
-# Output:
-# --- AGENT_BROWSER_PAGE_CONTENT nonce=<hex> origin=https://example.com ---
-# [accessibility tree]
-# --- END_AGENT_BROWSER_PAGE_CONTENT nonce=<hex> ---
-```
-
-### Domain Allowlist
-
-Restrict navigation to trusted domains. Wildcards like `*.example.com` also match the bare domain `example.com`. Sub-resource requests, WebSocket, and EventSource connections to non-allowed domains are also blocked. Include CDN domains your target pages depend on:
-
-```bash
-export AGENT_BROWSER_ALLOWED_DOMAINS="example.com,*.example.com"
-agent-browser open https://example.com        # OK
-agent-browser open https://malicious.com       # Blocked
-```
-
-### Action Policy
-
-Use a policy file to gate destructive actions:
-
-```bash
-export AGENT_BROWSER_ACTION_POLICY=./policy.json
-```
-
-Example `policy.json`:
-
-```json
-{ "default": "deny", "allow": ["navigate", "snapshot", "click", "scroll", "wait", "get"] }
-```
-
-Auth vault operations (`auth login`, etc.) bypass action policy but domain allowlist still applies.
-
-### Output Limits
-
-Prevent context flooding from large pages:
-
-```bash
-export AGENT_BROWSER_MAX_OUTPUT=50000
-```
-
-## Diffing (Verifying Changes)
-
-Use `diff snapshot` after performing an action to verify it had the intended effect. This compares the current accessibility tree against the last snapshot taken in the session.
-
-```bash
-# Typical workflow: snapshot -> action -> diff
-agent-browser snapshot -i          # Take baseline snapshot
-agent-browser click @e2            # Perform action
-agent-browser diff snapshot        # See what changed (auto-compares to last snapshot)
-```
-
-For visual regression testing or monitoring:
-
-```bash
-# Save a baseline screenshot, then compare later
-agent-browser screenshot baseline.png
-# ... time passes or changes are made ...
-agent-browser diff screenshot --baseline baseline.png
-
-# Compare staging vs production
-agent-browser diff url https://staging.example.com https://prod.example.com --screenshot
-```
-
-`diff snapshot` output uses `+` for additions and `-` for removals, similar to git diff. `diff screenshot` produces a diff image with changed pixels highlighted in red, plus a mismatch percentage.
-
-## Timeouts and Slow Pages
-
-The default timeout is 25 seconds. This can be overridden with the `AGENT_BROWSER_DEFAULT_TIMEOUT` environment variable (value in milliseconds). For slow websites or large pages, use explicit waits instead of relying on the default timeout:
-
-```bash
-# Wait for network activity to settle (best for slow pages)
-agent-browser wait --load networkidle
-
-# Wait for a specific element to appear
-agent-browser wait "#content"
-agent-browser wait @e1
-
-# Wait for a specific URL pattern (useful after redirects)
-agent-browser wait --url "**/dashboard"
-
-# Wait for a JavaScript condition
-agent-browser wait --fn "document.readyState === 'complete'"
-
-# Wait a fixed duration (milliseconds) as a last resort
-agent-browser wait 5000
-```
-
-When dealing with consistently slow websites, use `wait --load networkidle` after `open` to ensure the page is fully loaded before taking a snapshot. If a specific element is slow to render, wait for it directly with `wait <selector>` or `wait @ref`.
-
-## JavaScript Dialogs (alert / confirm / prompt)
-
-When a page opens a JavaScript dialog (`alert()`, `confirm()`, or `prompt()`), it blocks all other browser commands (snapshot, screenshot, click, etc.) until the dialog is dismissed. If commands start timing out unexpectedly, check for a pending dialog:
-
-```bash
-# Check if a dialog is blocking
-agent-browser dialog status
-
-# Accept the dialog (dismiss the alert / click OK)
-agent-browser dialog accept
-
-# Accept a prompt dialog with input text
-agent-browser dialog accept "my input"
-
-# Dismiss the dialog (click Cancel)
-agent-browser dialog dismiss
-```
-
-When a dialog is pending, all command responses include a `warning` field indicating the dialog type and message. In `--json` mode this appears as a `"warning"` key in the response object.
-
-## Session Management and Cleanup
-
-When running multiple agents or automations concurrently, always use named sessions to avoid conflicts:
-
-```bash
-# Each agent gets its own isolated session
-agent-browser --session agent1 open site-a.com
-agent-browser --session agent2 open site-b.com
-
-# Check active sessions
-agent-browser session list
-```
-
-Always close your browser session when done to avoid leaked processes:
-
-```bash
-agent-browser close                    # Close default session
-agent-browser --session agent1 close   # Close specific session
-```
-
-If a previous session was not closed properly, the daemon may still be running. Use `agent-browser close` to clean it up before starting new work.
-
-To auto-shutdown the daemon after a period of inactivity (useful for ephemeral/CI environments):
-
-```bash
-AGENT_BROWSER_IDLE_TIMEOUT_MS=60000 agent-browser open example.com
-```
-
-## Ref Lifecycle (Important)
-
-Refs (`@e1`, `@e2`, etc.) are invalidated when the page changes. Always re-snapshot after:
-
-- Clicking links or buttons that navigate
-- Form submissions
-- Dynamic content loading (dropdowns, modals)
-
-```bash
-agent-browser click @e5              # Navigates to new page
-agent-browser snapshot -i            # MUST re-snapshot
-agent-browser click @e1              # Use new refs
-```
-
-## Annotated Screenshots (Vision Mode)
-
-Use `--annotate` to take a screenshot with numbered labels overlaid on interactive elements. Each label `[N]` maps to ref `@eN`. This also caches refs, so you can interact with elements immediately without a separate snapshot.
-
-```bash
-agent-browser screenshot --annotate
-# Output includes the image path and a legend:
-#   [1] @e1 button "Submit"
-#   [2] @e2 link "Home"
-#   [3] @e3 textbox "Email"
-agent-browser click @e2              # Click using ref from annotated screenshot
-```
-
-Use annotated screenshots when:
-
-- The page has unlabeled icon buttons or visual-only elements
-- You need to verify visual layout or styling
-- Canvas or chart elements are present (invisible to text snapshots)
-- You need spatial reasoning about element positions
-
-## Semantic Locators (Alternative to Refs)
-
-When refs are unavailable or unreliable, use semantic locators:
-
-```bash
-agent-browser find text "Sign In" click
-agent-browser find label "Email" fill "user@test.com"
-agent-browser find role button click --name "Submit"
-agent-browser find placeholder "Search" type "query"
-agent-browser find testid "submit-btn" click
-```
-
-## JavaScript Evaluation (eval)
-
-Use `eval` to run JavaScript in the browser context. **Shell quoting can corrupt complex expressions** -- use `--stdin` or `-b` to avoid issues.
-
-```bash
-# Simple expressions work with regular quoting
-agent-browser eval 'document.title'
-agent-browser eval 'document.querySelectorAll("img").length'
-
-# Complex JS: use --stdin with heredoc (RECOMMENDED)
-agent-browser eval --stdin <<'EVALEOF'
-JSON.stringify(
-  Array.from(document.querySelectorAll("img"))
-    .filter(i => !i.alt)
-    .map(i => ({ src: i.src.split("/").pop(), width: i.width }))
-)
-EVALEOF
-
-# Alternative: base64 encoding (avoids all shell escaping issues)
-agent-browser eval -b "$(echo -n 'Array.from(document.querySelectorAll("a")).map(a => a.href)' | base64)"
-```
-
-**Why this matters:** When the shell processes your command, inner double quotes, `!` characters (history expansion), backticks, and `$()` can all corrupt the JavaScript before it reaches agent-browser. The `--stdin` and `-b` flags bypass shell interpretation entirely.
-
-**Rules of thumb:**
-
-- Single-line, no nested quotes -> regular `eval 'expression'` with single quotes is fine
-- Nested quotes, arrow functions, template literals, or multiline -> use `eval --stdin <<'EVALEOF'`
-- Programmatic/generated scripts -> use `eval -b` with base64
-
-## Configuration File
-
-Create `agent-browser.json` in the project root for persistent settings:
-
-```json
-{
-  "headed": true,
-  "proxy": "http://localhost:8080",
-  "profile": "./browser-data"
-}
-```
-
-Priority (lowest to highest): `~/.agent-browser/config.json` < `./agent-browser.json` < env vars < CLI flags. Use `--config <path>` or `AGENT_BROWSER_CONFIG` env var for a custom config file (exits with error if missing/invalid). All CLI options map to camelCase keys (e.g., `--executable-path` -> `"executablePath"`). Boolean flags accept `true`/`false` values (e.g., `--headed false` overrides config). Extensions from user and project configs are merged, not replaced.
-
-## Deep-Dive Documentation
-
-| Reference                                                            | When to Use                                               |
-| -------------------------------------------------------------------- | --------------------------------------------------------- |
-| [references/commands.md](references/commands.md)                     | Full command reference with all options                   |
-| [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/profiling.md](references/profiling.md)                   | Chrome DevTools profiling for performance analysis        |
-| [references/proxy-support.md](references/proxy-support.md)           | Proxy configuration, geo-testing, rotating proxies        |
-
-## Browser Engine Selection
-
-Use `--engine` to choose a local browser engine. The default is `chrome`.
-
-```bash
-# Use Lightpanda (fast headless browser, requires separate install)
-agent-browser --engine lightpanda open example.com
-
-# Via environment variable
-export AGENT_BROWSER_ENGINE=lightpanda
-agent-browser open example.com
-
-# With custom binary path
-agent-browser --engine lightpanda --executable-path /path/to/lightpanda open example.com
-```
-
-Supported engines:
-- `chrome` (default) -- Chrome/Chromium via CDP
-- `lightpanda` -- Lightpanda headless browser via CDP (10x faster, 10x less memory than Chrome)
-
-Lightpanda does not support `--extension`, `--profile`, `--state`, or `--allow-file-access`. Install Lightpanda from https://lightpanda.io/docs/open-source/installation.
-
-## Ready-to-Use Templates
-
-| 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 |
-
-```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
-```

+ 0 - 303
neo/.agents/skills/agent-browser/references/authentication.md

@@ -1,303 +0,0 @@
-# Authentication Patterns
-
-Login flows, session persistence, OAuth, 2FA, and authenticated browsing.
-
-**Related**: [session-management.md](session-management.md) for state persistence details, [SKILL.md](../SKILL.md) for quick start.
-
-## Contents
-
-- [Import Auth from Your Browser](#import-auth-from-your-browser)
-- [Persistent Profiles](#persistent-profiles)
-- [Session Persistence](#session-persistence)
-- [Basic Login Flow](#basic-login-flow)
-- [Saving Authentication State](#saving-authentication-state)
-- [Restoring Authentication](#restoring-authentication)
-- [OAuth / SSO Flows](#oauth--sso-flows)
-- [Two-Factor Authentication](#two-factor-authentication)
-- [HTTP Basic Auth](#http-basic-auth)
-- [Cookie-Based Auth](#cookie-based-auth)
-- [Token Refresh Handling](#token-refresh-handling)
-- [Security Best Practices](#security-best-practices)
-
-## Import Auth from Your Browser
-
-The fastest way to authenticate is to reuse cookies from a Chrome session you are already logged into.
-
-**Step 1: Start Chrome with remote debugging**
-
-```bash
-# macOS
-"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --remote-debugging-port=9222
-
-# Linux
-google-chrome --remote-debugging-port=9222
-
-# Windows
-"C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222
-```
-
-Log in to your target site(s) in this Chrome window as you normally would.
-
-> **Security note:** `--remote-debugging-port` exposes full browser control on localhost. Any local process can connect and read cookies, execute JS, etc. Only use on trusted machines and close Chrome when done.
-
-**Step 2: Grab the auth state**
-
-```bash
-# Auto-discover the running Chrome and save its cookies + localStorage
-agent-browser --auto-connect state save ./my-auth.json
-```
-
-**Step 3: Reuse in automation**
-
-```bash
-# Load auth at launch
-agent-browser --state ./my-auth.json open https://app.example.com/dashboard
-
-# Or load into an existing session
-agent-browser state load ./my-auth.json
-agent-browser open https://app.example.com/dashboard
-```
-
-This works for any site, including those with complex OAuth flows, SSO, or 2FA -- as long as Chrome already has valid session cookies.
-
-> **Security note:** State files contain session tokens in plaintext. Add them to `.gitignore`, delete when no longer needed, and set `AGENT_BROWSER_ENCRYPTION_KEY` for encryption at rest. See [Security Best Practices](#security-best-practices).
-
-**Tip:** Combine with `--session-name` so the imported auth auto-persists across restarts:
-
-```bash
-agent-browser --session-name myapp state load ./my-auth.json
-# From now on, state is auto-saved/restored for "myapp"
-```
-
-## Persistent Profiles
-
-Use `--profile` to point agent-browser at a Chrome user data directory. This persists everything (cookies, IndexedDB, service workers, cache) across browser restarts without explicit save/load:
-
-```bash
-# First run: login once
-agent-browser --profile ~/.myapp-profile open https://app.example.com/login
-# ... complete login flow ...
-
-# All subsequent runs: already authenticated
-agent-browser --profile ~/.myapp-profile open https://app.example.com/dashboard
-```
-
-Use different paths for different projects or test users:
-
-```bash
-agent-browser --profile ~/.profiles/admin open https://app.example.com
-agent-browser --profile ~/.profiles/viewer open https://app.example.com
-```
-
-Or set via environment variable:
-
-```bash
-export AGENT_BROWSER_PROFILE=~/.myapp-profile
-agent-browser open https://app.example.com/dashboard
-```
-
-## Session Persistence
-
-Use `--session-name` to auto-save and restore cookies + localStorage by name, without managing files:
-
-```bash
-# Auto-saves state on close, auto-restores on next launch
-agent-browser --session-name twitter open https://twitter.com
-# ... login flow ...
-agent-browser close  # state saved to ~/.agent-browser/sessions/
-
-# Next time: state is automatically restored
-agent-browser --session-name twitter open https://twitter.com
-```
-
-Encrypt state at rest:
-
-```bash
-export AGENT_BROWSER_ENCRYPTION_KEY=$(openssl rand -hex 32)
-agent-browser --session-name secure open https://app.example.com
-```
-
-## 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
-   ```

+ 0 - 292
neo/.agents/skills/agent-browser/references/commands.md

@@ -1,292 +0,0 @@
-# Command Reference
-
-Complete reference for all agent-browser commands. For quick start and common patterns, see SKILL.md.
-
-## Navigation
-
-```bash
-agent-browser open <url>      # 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 click @e1 --new-tab # Click and open in new tab
-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 cdp-url         # Get CDP WebSocket 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 and PDF
-
-```bash
-agent-browser screenshot          # Save to temporary directory
-agent-browser screenshot path.png # Save to 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
-agent-browser click @e1                   # Perform actions
-agent-browser record stop                 # Stop and save video
-agent-browser record restart ./take2.webm # Stop current + start new
-```
-
-## 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 viewport 1920 1080 2        # 2x retina (same CSS size, higher res screenshots)
-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 and 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 <url>              # Intercept requests
-agent-browser network route <url> --abort      # Block requests
-agent-browser network route <url> --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 and 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 by CSS selector
-agent-browser frame @e3           # Switch to iframe by element ref
-agent-browser frame main          # Back to main frame
-```
-
-### Iframe support
-
-Iframes are detected automatically during snapshots. When the main-frame snapshot runs, `Iframe` nodes are resolved and their content is inlined beneath the iframe element in the output (one level of nesting; iframes within iframes are not expanded).
-
-```bash
-agent-browser snapshot -i
-# @e3 [Iframe] "payment-frame"
-#   @e4 [input] "Card number"
-#   @e5 [button] "Pay"
-
-# Interact directly — refs inside iframes already work
-agent-browser fill @e4 "4111111111111111"
-agent-browser click @e5
-
-# Or switch frame context for scoped snapshots
-agent-browser frame @e3               # Switch using element ref
-agent-browser snapshot -i             # Snapshot scoped to that iframe
-agent-browser frame main              # Return to main frame
-```
-
-The `frame` command accepts:
-- **Element refs** — `frame @e3` resolves the ref to an iframe element
-- **CSS selectors** — `frame "#payment-iframe"` finds the iframe by selector
-- **Frame name/URL** — matches against the browser's frame tree
-
-## Dialogs
-
-```bash
-agent-browser dialog accept [text]  # Accept dialog
-agent-browser dialog dismiss        # Dismiss dialog
-```
-
-## JavaScript
-
-```bash
-agent-browser eval "document.title"          # Simple expressions only
-agent-browser eval -b "<base64>"             # Any JavaScript (base64 encoded)
-agent-browser eval --stdin                   # Read script from stdin
-```
-
-Use `-b`/`--base64` or `--stdin` for reliable execution. Shell escaping with nested quotes and special characters is error-prone.
-
-```bash
-# Base64 encode your script, then:
-agent-browser eval -b "ZG9jdW1lbnQucXVlcnlTZWxlY3RvcignW3NyYyo9Il9uZXh0Il0nKQ=="
-
-# Or use stdin with heredoc for multiline scripts:
-cat <<'EOF' | agent-browser eval --stdin
-const links = document.querySelectorAll('a');
-Array.from(links).map(a => a.href);
-EOF
-```
-
-## State Management
-
-```bash
-agent-browser state save auth.json    # Save cookies, storage, auth state
-agent-browser state load auth.json    # Restore saved state
-```
-
-## Global Options
-
-```bash
-agent-browser --session <name> ...    # 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 <port> ...        # Connect via Chrome DevTools Protocol
-agent-browser -p <provider> ...       # Cloud browser provider (--provider)
-agent-browser --proxy <url> ...       # Use proxy server
-agent-browser --proxy-bypass <hosts>  # Hosts to bypass proxy
-agent-browser --headers <json> ...    # HTTP headers scoped to URL's origin
-agent-browser --executable-path <p>   # Custom browser executable
-agent-browser --extension <path> ...  # Load browser extension (repeatable)
-agent-browser --ignore-https-errors   # Ignore SSL certificate errors
-agent-browser --help                  # Show help (-h)
-agent-browser --version               # Show version (-V)
-agent-browser <command> --help        # Show detailed help for a command
-```
-
-## 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 inspect                     # Open Chrome DevTools for this session
-agent-browser trace start                 # Start recording trace
-agent-browser trace stop trace.zip        # Stop and save trace
-agent-browser profiler start              # Start Chrome DevTools profiling
-agent-browser profiler stop trace.json    # Stop and save profile
-```
-
-## 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="browserbase"         # Cloud browser provider
-AGENT_BROWSER_STREAM_PORT="9223"             # WebSocket streaming port
-AGENT_BROWSER_HOME="/path/to/agent-browser"  # Custom install location
-```

+ 0 - 120
neo/.agents/skills/agent-browser/references/profiling.md

@@ -1,120 +0,0 @@
-# Profiling
-
-Capture Chrome DevTools performance profiles during browser automation for performance analysis.
-
-**Related**: [commands.md](commands.md) for full command reference, [SKILL.md](../SKILL.md) for quick start.
-
-## Contents
-
-- [Basic Profiling](#basic-profiling)
-- [Profiler Commands](#profiler-commands)
-- [Categories](#categories)
-- [Use Cases](#use-cases)
-- [Output Format](#output-format)
-- [Viewing Profiles](#viewing-profiles)
-- [Limitations](#limitations)
-
-## Basic Profiling
-
-```bash
-# Start profiling
-agent-browser profiler start
-
-# Perform actions
-agent-browser navigate https://example.com
-agent-browser click "#button"
-agent-browser wait 1000
-
-# Stop and save
-agent-browser profiler stop ./trace.json
-```
-
-## Profiler Commands
-
-```bash
-# Start profiling with default categories
-agent-browser profiler start
-
-# Start with custom trace categories
-agent-browser profiler start --categories "devtools.timeline,v8.execute,blink.user_timing"
-
-# Stop profiling and save to file
-agent-browser profiler stop ./trace.json
-```
-
-## Categories
-
-The `--categories` flag accepts a comma-separated list of Chrome trace categories. Default categories include:
-
-- `devtools.timeline` -- standard DevTools performance traces
-- `v8.execute` -- time spent running JavaScript
-- `blink` -- renderer events
-- `blink.user_timing` -- `performance.mark()` / `performance.measure()` calls
-- `latencyInfo` -- input-to-latency tracking
-- `renderer.scheduler` -- task scheduling and execution
-- `toplevel` -- broad-spectrum basic events
-
-Several `disabled-by-default-*` categories are also included for detailed timeline, call stack, and V8 CPU profiling data.
-
-## Use Cases
-
-### Diagnosing Slow Page Loads
-
-```bash
-agent-browser profiler start
-agent-browser navigate https://app.example.com
-agent-browser wait --load networkidle
-agent-browser profiler stop ./page-load-profile.json
-```
-
-### Profiling User Interactions
-
-```bash
-agent-browser navigate https://app.example.com
-agent-browser profiler start
-agent-browser click "#submit"
-agent-browser wait 2000
-agent-browser profiler stop ./interaction-profile.json
-```
-
-### CI Performance Regression Checks
-
-```bash
-#!/bin/bash
-agent-browser profiler start
-agent-browser navigate https://app.example.com
-agent-browser wait --load networkidle
-agent-browser profiler stop "./profiles/build-${BUILD_ID}.json"
-```
-
-## Output Format
-
-The output is a JSON file in Chrome Trace Event format:
-
-```json
-{
-  "traceEvents": [
-    { "cat": "devtools.timeline", "name": "RunTask", "ph": "X", "ts": 12345, "dur": 100, ... },
-    ...
-  ],
-  "metadata": {
-    "clock-domain": "LINUX_CLOCK_MONOTONIC"
-  }
-}
-```
-
-The `metadata.clock-domain` field is set based on the host platform (Linux or macOS). On Windows it is omitted.
-
-## Viewing Profiles
-
-Load the output JSON file in any of these tools:
-
-- **Chrome DevTools**: Performance panel > Load profile (Ctrl+Shift+I > Performance)
-- **Perfetto UI**: https://ui.perfetto.dev/ -- drag and drop the JSON file
-- **Trace Viewer**: `chrome://tracing` in any Chromium browser
-
-## Limitations
-
-- Only works with Chromium-based browsers (Chrome, Edge). Not supported on Firefox or WebKit.
-- Trace data accumulates in memory while profiling is active (capped at 5 million events). Stop profiling promptly after the area of interest.
-- Data collection on stop has a 30-second timeout. If the browser is unresponsive, the stop command may fail.

+ 0 - 194
neo/.agents/skills/agent-browser/references/proxy-support.md

@@ -1,194 +0,0 @@
-# Proxy Support
-
-Proxy configuration for geo-testing, rate limiting avoidance, and corporate environments.
-
-**Related**: [commands.md](commands.md) for global options, [SKILL.md](../SKILL.md) for quick start.
-
-## Contents
-
-- [Basic Proxy Configuration](#basic-proxy-configuration)
-- [Authenticated Proxy](#authenticated-proxy)
-- [SOCKS Proxy](#socks-proxy)
-- [Proxy Bypass](#proxy-bypass)
-- [Common Use Cases](#common-use-cases)
-- [Verifying Proxy Connection](#verifying-proxy-connection)
-- [Troubleshooting](#troubleshooting)
-- [Best Practices](#best-practices)
-
-## Basic Proxy Configuration
-
-Use the `--proxy` flag or set proxy via environment variable:
-
-```bash
-# Via CLI flag
-agent-browser --proxy "http://proxy.example.com:8080" open https://example.com
-
-# Via environment variable
-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 using `--proxy-bypass` or `NO_PROXY`:
-
-```bash
-# Via CLI flag
-agent-browser --proxy "http://proxy.example.com:8080" --proxy-bypass "localhost,*.internal.com" open https://example.com
-
-# Via environment variable
-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

+ 0 - 193
neo/.agents/skills/agent-browser/references/session-management.md

@@ -1,193 +0,0 @@
-# Session Management
-
-Multiple isolated browser sessions with state persistence and concurrent browsing.
-
-**Related**: [authentication.md](authentication.md) for login patterns, [SKILL.md](../SKILL.md) for quick start.
-
-## Contents
-
-- [Named Sessions](#named-sessions)
-- [Session Isolation Properties](#session-isolation-properties)
-- [Session State Persistence](#session-state-persistence)
-- [Common Patterns](#common-patterns)
-- [Default Session](#default-session)
-- [Session Cleanup](#session-cleanup)
-- [Best Practices](#best-practices)
-
-## 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
-```

+ 0 - 219
neo/.agents/skills/agent-browser/references/snapshot-refs.md

@@ -1,219 +0,0 @@
-# Snapshot and Refs
-
-Compact element references that reduce context usage dramatically for AI agents.
-
-**Related**: [commands.md](commands.md) for full command reference, [SKILL.md](../SKILL.md) for quick start.
-
-## Contents
-
-- [How Refs Work](#how-refs-work)
-- [Snapshot Command](#the-snapshot-command)
-- [Using Refs](#using-refs)
-- [Ref Lifecycle](#ref-lifecycle)
-- [Best Practices](#best-practices)
-- [Ref Notation Details](#ref-notation-details)
-- [Troubleshooting](#troubleshooting)
-
-## How Refs Work
-
-Traditional approach:
-```
-Full DOM/HTML → AI parses → CSS selector → Action (~3000-5000 tokens)
-```
-
-agent-browser approach:
-```
-Compact snapshot → @refs assigned → Direct interaction (~200-400 tokens)
-```
-
-## 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
-```
-
-## Iframes
-
-Snapshots automatically detect and inline iframe content. When the main-frame snapshot runs, each `Iframe` node is resolved and its child accessibility tree is included directly beneath it in the output. Refs assigned to elements inside iframes carry frame context, so interactions like `click`, `fill`, and `type` work without manually switching frames.
-
-```bash
-agent-browser snapshot -i
-# @e1 [heading] "Checkout"
-# @e2 [Iframe] "payment-frame"
-#   @e3 [input] "Card number"
-#   @e4 [input] "Expiry"
-#   @e5 [button] "Pay"
-# @e6 [button] "Cancel"
-
-# Interact with iframe elements directly using their refs
-agent-browser fill @e3 "4111111111111111"
-agent-browser fill @e4 "12/28"
-agent-browser click @e5
-```
-
-**Key details:**
-- Only one level of iframe nesting is expanded (iframes within iframes are not recursed)
-- Cross-origin iframes that block accessibility tree access are silently skipped
-- Empty iframes or iframes with no interactive content are omitted from the output
-- To scope a snapshot to a single iframe, use `frame @ref` then `snapshot -i`
-
-## Troubleshooting
-
-### "Ref not found" Error
-
-```bash
-# Ref may have changed - re-snapshot
-agent-browser snapshot -i
-```
-
-### Element Not Visible in Snapshot
-
-```bash
-# Scroll down to reveal element
-agent-browser scroll down 1000
-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
-```

+ 0 - 173
neo/.agents/skills/agent-browser/references/video-recording.md

@@ -1,173 +0,0 @@
-# Video Recording
-
-Capture browser automation as video for debugging, documentation, or verification.
-
-**Related**: [commands.md](commands.md) for full command reference, [SKILL.md](../SKILL.md) for quick start.
-
-## Contents
-
-- [Basic Recording](#basic-recording)
-- [Recording Commands](#recording-commands)
-- [Use Cases](#use-cases)
-- [Best Practices](#best-practices)
-- [Output Format](#output-format)
-- [Limitations](#limitations)
-
-## 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

+ 0 - 105
neo/.agents/skills/agent-browser/templates/authenticated-session.sh

@@ -1,105 +0,0 @@
-#!/bin/bash
-# Template: Authenticated Session Workflow
-# Purpose: Login once, save state, reuse for subsequent runs
-# Usage: ./authenticated-session.sh <login-url> [state-file]
-#
-# RECOMMENDED: Use the auth vault instead of this template:
-#   echo "<pass>" | agent-browser auth save myapp --url <login-url> --username <user> --password-stdin
-#   agent-browser auth login myapp
-# The auth vault stores credentials securely and the LLM never sees passwords.
-#
-# Environment variables:
-#   APP_USERNAME - Login username/email
-#   APP_PASSWORD - Login password
-#
-# Two modes:
-#   1. Discovery mode (default): Shows form structure so you can identify refs
-#   2. Login mode: Performs actual login after you update the refs
-#
-# Setup steps:
-#   1. Run once to see form structure (discovery mode)
-#   2. Update refs in LOGIN FLOW section below
-#   3. Set APP_USERNAME and APP_PASSWORD
-#   4. Delete the DISCOVERY section
-
-set -euo pipefail
-
-LOGIN_URL="${1:?Usage: $0 <login-url> [state-file]}"
-STATE_FILE="${2:-./auth-state.json}"
-
-echo "Authentication workflow: $LOGIN_URL"
-
-# ================================================================
-# SAVED STATE: Skip login if valid saved state exists
-# ================================================================
-if [[ -f "$STATE_FILE" ]]; then
-    echo "Loading saved state from $STATE_FILE..."
-    if agent-browser --state "$STATE_FILE" open "$LOGIN_URL" 2>/dev/null; then
-        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..."
-        agent-browser close 2>/dev/null || true
-    else
-        echo "Failed to load state, re-authenticating..."
-    fi
-    rm -f "$STATE_FILE"
-fi
-
-# ================================================================
-# DISCOVERY MODE: Shows form structure (delete after setup)
-# ================================================================
-echo "Opening login page..."
-agent-browser open "$LOGIN_URL"
-agent-browser wait --load networkidle
-
-echo ""
-echo "Login form structure:"
-echo "---"
-agent-browser snapshot -i
-echo "---"
-echo ""
-echo "Next steps:"
-echo "  1. Note the refs: username=@e?, password=@e?, submit=@e?"
-echo "  2. Update the LOGIN FLOW section below with your refs"
-echo "  3. Set: export APP_USERNAME='...' APP_PASSWORD='...'"
-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 "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 state to $STATE_FILE"
-# agent-browser state save "$STATE_FILE"
-# echo "Login successful"
-# agent-browser snapshot -i

+ 0 - 69
neo/.agents/skills/agent-browser/templates/capture-workflow.sh

@@ -1,69 +0,0 @@
-#!/bin/bash
-# Template: Content Capture Workflow
-# Purpose: Extract content from web pages (text, screenshots, PDF)
-# Usage: ./capture-workflow.sh <url> [output-dir]
-#
-# Outputs:
-#   - page-full.png: Full page screenshot
-#   - page-structure.txt: Page element structure with refs
-#   - page-text.txt: All text content
-#   - page.pdf: PDF version
-#
-# Optional: Load auth state for protected pages
-
-set -euo pipefail
-
-TARGET_URL="${1:?Usage: $0 <url> [output-dir]}"
-OUTPUT_DIR="${2:-.}"
-
-echo "Capturing: $TARGET_URL"
-mkdir -p "$OUTPUT_DIR"
-
-# Optional: Load authentication state
-# if [[ -f "./auth-state.json" ]]; then
-#     echo "Loading authentication state..."
-#     agent-browser state load "./auth-state.json"
-# fi
-
-# Navigate to target
-agent-browser open "$TARGET_URL"
-agent-browser wait --load networkidle
-
-# Get metadata
-TITLE=$(agent-browser get title)
-URL=$(agent-browser get url)
-echo "Title: $TITLE"
-echo "URL: $URL"
-
-# Capture full page screenshot
-agent-browser screenshot --full "$OUTPUT_DIR/page-full.png"
-echo "Saved: $OUTPUT_DIR/page-full.png"
-
-# Get page structure with refs
-agent-browser snapshot -i > "$OUTPUT_DIR/page-structure.txt"
-echo "Saved: $OUTPUT_DIR/page-structure.txt"
-
-# Extract all text content
-agent-browser get text body > "$OUTPUT_DIR/page-text.txt"
-echo "Saved: $OUTPUT_DIR/page-text.txt"
-
-# Save as PDF
-agent-browser pdf "$OUTPUT_DIR/page.pdf"
-echo "Saved: $OUTPUT_DIR/page.pdf"
-
-# Optional: Extract specific elements using refs from structure
-# agent-browser get text @e5 > "$OUTPUT_DIR/main-content.txt"
-
-# Optional: Handle infinite scroll pages
-# for i in {1..5}; do
-#     agent-browser scroll down 1000
-#     agent-browser wait 1000
-# done
-# agent-browser screenshot --full "$OUTPUT_DIR/page-scrolled.png"
-
-# Cleanup
-agent-browser close
-
-echo ""
-echo "Capture complete:"
-ls -la "$OUTPUT_DIR"

+ 0 - 62
neo/.agents/skills/agent-browser/templates/form-automation.sh

@@ -1,62 +0,0 @@
-#!/bin/bash
-# Template: Form Automation Workflow
-# Purpose: Fill and submit web forms with validation
-# Usage: ./form-automation.sh <form-url>
-#
-# This template demonstrates the snapshot-interact-verify pattern:
-# 1. Navigate to form
-# 2. Snapshot to get element refs
-# 3. Fill fields using refs
-# 4. Submit and verify result
-#
-# Customize: Update the refs (@e1, @e2, etc.) based on your form's snapshot output
-
-set -euo pipefail
-
-FORM_URL="${1:?Usage: $0 <form-url>}"
-
-echo "Form automation: $FORM_URL"
-
-# Step 1: Navigate to form
-agent-browser open "$FORM_URL"
-agent-browser wait --load networkidle
-
-# Step 2: Snapshot to discover form elements
-echo ""
-echo "Form structure:"
-agent-browser snapshot -i
-
-# Step 3: Fill form fields (customize these refs based on snapshot output)
-#
-# Common field types:
-#   agent-browser fill @e1 "John Doe"           # Text input
-#   agent-browser fill @e2 "user@example.com"   # Email input
-#   agent-browser fill @e3 "SecureP@ss123"      # Password input
-#   agent-browser select @e4 "Option Value"     # Dropdown
-#   agent-browser check @e5                     # Checkbox
-#   agent-browser click @e6                     # Radio button
-#   agent-browser fill @e7 "Multi-line text"   # Textarea
-#   agent-browser upload @e8 /path/to/file.pdf # File upload
-#
-# Uncomment and modify:
-# agent-browser fill @e1 "Test User"
-# agent-browser fill @e2 "test@example.com"
-# agent-browser click @e3  # Submit button
-
-# Step 4: Wait for submission
-# agent-browser wait --load networkidle
-# agent-browser wait --url "**/success"  # Or wait for redirect
-
-# Step 5: Verify result
-echo ""
-echo "Result:"
-agent-browser get url
-agent-browser snapshot -i
-
-# Optional: Capture evidence
-agent-browser screenshot /tmp/form-result.png
-echo "Screenshot saved: /tmp/form-result.png"
-
-# Cleanup
-agent-browser close
-echo "Done"

+ 0 - 321
neo/.agents/skills/building-native-ui/SKILL.md

@@ -1,321 +0,0 @@
----
-name: building-native-ui
-description: Complete guide for building beautiful apps with Expo Router. Covers fundamentals, styling, components, navigation, animations, patterns, and native tabs.
-version: 1.0.1
-license: MIT
----
-
-# Expo UI Guidelines
-
-## References
-
-Consult these resources as needed:
-
-```
-references/
-  animations.md          Reanimated: entering, exiting, layout, scroll-driven, gestures
-  controls.md            Native iOS: Switch, Slider, SegmentedControl, DateTimePicker, Picker
-  form-sheet.md          Form sheets in expo-router: configuration, footers and background interaction. 
-  gradients.md           CSS gradients via experimental_backgroundImage (New Arch only)
-  icons.md               SF Symbols via expo-image (sf: source), names, animations, weights
-  media.md               Camera, audio, video, and file saving
-  route-structure.md     Route conventions, dynamic routes, groups, folder organization
-  search.md              Search bar with headers, useSearch hook, filtering patterns
-  storage.md             SQLite, AsyncStorage, SecureStore
-  tabs.md                NativeTabs, migration from JS tabs, iOS 26 features
-  toolbar-and-headers.md Stack headers and toolbar buttons, menus, search (iOS only)
-  visual-effects.md      Blur (expo-blur) and liquid glass (expo-glass-effect)
-  webgpu-three.md        3D graphics, games, GPU visualizations with WebGPU and Three.js
-  zoom-transitions.md    Apple Zoom: fluid zoom transitions with Link.AppleZoom (iOS 18+)
-```
-
-## Running the App
-
-**CRITICAL: Always try Expo Go first before creating custom builds.**
-
-Most Expo apps work in Expo Go without any custom native code. Before running `npx expo run:ios` or `npx expo run:android`:
-
-1. **Start with Expo Go**: Run `npx expo start` and scan the QR code with Expo Go
-2. **Check if features work**: Test your app thoroughly in Expo Go
-3. **Only create custom builds when required** - see below
-
-### When Custom Builds Are Required
-
-You need `npx expo run:ios/android` or `eas build` ONLY when using:
-
-- **Local Expo modules** (custom native code in `modules/`)
-- **Apple targets** (widgets, app clips, extensions via `@bacons/apple-targets`)
-- **Third-party native modules** not included in Expo Go
-- **Custom native configuration** that can't be expressed in `app.json`
-
-### When Expo Go Works
-
-Expo Go supports a huge range of features out of the box:
-
-- All `expo-*` packages (camera, location, notifications, etc.)
-- Expo Router navigation
-- Most UI libraries (reanimated, gesture handler, etc.)
-- Push notifications, deep links, and more
-
-**If you're unsure, try Expo Go first.** Creating custom builds adds complexity, slower iteration, and requires Xcode/Android Studio setup.
-
-## Code Style
-
-- Be cautious of unterminated strings. Ensure nested backticks are escaped; never forget to escape quotes correctly.
-- Always use import statements at the top of the file.
-- Always use kebab-case for file names, e.g. `comment-card.tsx`
-- Always remove old route files when moving or restructuring navigation
-- Never use special characters in file names
-- Configure tsconfig.json with path aliases, and prefer aliases over relative imports for refactors.
-
-## Routes
-
-See `./references/route-structure.md` for detailed route conventions.
-
-- Routes belong in the `app` directory.
-- Never co-locate components, types, or utilities in the app directory. This is an anti-pattern.
-- Ensure the app always has a route that matches "/", it may be inside a group route.
-
-## Library Preferences
-
-- Never use modules removed from React Native such as Picker, WebView, SafeAreaView, or AsyncStorage
-- Never use legacy expo-permissions
-- `expo-audio` not `expo-av`
-- `expo-video` not `expo-av`
-- `expo-image` with `source="sf:name"` for SF Symbols, not `expo-symbols` or `@expo/vector-icons`
-- `react-native-safe-area-context` not react-native SafeAreaView
-- `process.env.EXPO_OS` not `Platform.OS`
-- `React.use` not `React.useContext`
-- `expo-image` Image component instead of intrinsic element `img`
-- `expo-glass-effect` for liquid glass backdrops
-
-## Responsiveness
-
-- Always wrap root component in a scroll view for responsiveness
-- Use `<ScrollView contentInsetAdjustmentBehavior="automatic" />` instead of `<SafeAreaView>` for smarter safe area insets
-- `contentInsetAdjustmentBehavior="automatic"` should be applied to FlatList and SectionList as well
-- Use flexbox instead of Dimensions API
-- ALWAYS prefer `useWindowDimensions` over `Dimensions.get()` to measure screen size
-
-## Behavior
-
-- Use expo-haptics conditionally on iOS to make more delightful experiences
-- Use views with built-in haptics like `<Switch />` from React Native and `@react-native-community/datetimepicker`
-- When a route belongs to a Stack, its first child should almost always be a ScrollView with `contentInsetAdjustmentBehavior="automatic"` set
-- When adding a `ScrollView` to the page it should almost always be the first component inside the route component
-- Prefer `headerSearchBarOptions` in Stack.Screen options to add a search bar
-- Use the `<Text selectable />` prop on text containing data that could be copied
-- Consider formatting large numbers like 1.4M or 38k
-- Never use intrinsic elements like 'img' or 'div' unless in a webview or Expo DOM component
-
-# Styling
-
-Follow Apple Human Interface Guidelines.
-
-## General Styling Rules
-
-- Prefer flex gap over margin and padding styles
-- Prefer padding over margin where possible
-- Always account for safe area, either with stack headers, tabs, or ScrollView/FlatList `contentInsetAdjustmentBehavior="automatic"`
-- Ensure both top and bottom safe area insets are accounted for
-- Inline styles not StyleSheet.create unless reusing styles is faster
-- Add entering and exiting animations for state changes
-- Use `{ borderCurve: 'continuous' }` for rounded corners unless creating a capsule shape
-- ALWAYS use a navigation stack title instead of a custom text element on the page
-- When padding a ScrollView, use `contentContainerStyle` padding and gap instead of padding on the ScrollView itself (reduces clipping)
-- CSS and Tailwind are not supported - use inline styles
-
-## Text Styling
-
-- Add the `selectable` prop to every `<Text/>` element displaying important data or error messages
-- Counters should use `{ fontVariant: 'tabular-nums' }` for alignment
-
-## Shadows
-
-Use CSS `boxShadow` style prop. NEVER use legacy React Native shadow or elevation styles.
-
-```tsx
-<View style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }} />
-```
-
-'inset' shadows are supported.
-
-# Navigation
-
-## Link
-
-Use `<Link href="/path" />` from 'expo-router' for navigation between routes.
-
-```tsx
-import { Link } from 'expo-router';
-
-// Basic link
-<Link href="/path" />
-
-// Wrapping custom components
-<Link href="/path" asChild>
-  <Pressable>...</Pressable>
-</Link>
-```
-
-Whenever possible, include a `<Link.Preview>` to follow iOS conventions. Add context menus and previews frequently to enhance navigation.
-
-## Stack
-
-- ALWAYS use `_layout.tsx` files to define stacks
-- Use Stack from 'expo-router/stack' for native navigation stacks
-
-### Page Title
-
-Set the page title in Stack.Screen options:
-
-```tsx
-<Stack.Screen options={{ title: "Home" }} />
-```
-
-## Context Menus
-
-Add long press context menus to Link components:
-
-```tsx
-import { Link } from "expo-router";
-
-<Link href="/settings" asChild>
-  <Link.Trigger>
-    <Pressable>
-      <Card />
-    </Pressable>
-  </Link.Trigger>
-  <Link.Menu>
-    <Link.MenuAction
-      title="Share"
-      icon="square.and.arrow.up"
-      onPress={handleSharePress}
-    />
-    <Link.MenuAction
-      title="Block"
-      icon="nosign"
-      destructive
-      onPress={handleBlockPress}
-    />
-    <Link.Menu title="More" icon="ellipsis">
-      <Link.MenuAction title="Copy" icon="doc.on.doc" onPress={() => {}} />
-      <Link.MenuAction
-        title="Delete"
-        icon="trash"
-        destructive
-        onPress={() => {}}
-      />
-    </Link.Menu>
-  </Link.Menu>
-</Link>;
-```
-
-## Link Previews
-
-Use link previews frequently to enhance navigation:
-
-```tsx
-<Link href="/settings">
-  <Link.Trigger>
-    <Pressable>
-      <Card />
-    </Pressable>
-  </Link.Trigger>
-  <Link.Preview />
-</Link>
-```
-
-Link preview can be used with context menus.
-
-## Modal
-
-Present a screen as a modal:
-
-```tsx
-<Stack.Screen name="modal" options={{ presentation: "modal" }} />
-```
-
-Prefer this to building a custom modal component.
-
-## Sheet
-
-Present a screen as a dynamic form sheet:
-
-```tsx
-<Stack.Screen
-  name="sheet"
-  options={{
-    presentation: "formSheet",
-    sheetGrabberVisible: true,
-    sheetAllowedDetents: [0.5, 1.0],
-    contentStyle: { backgroundColor: "transparent" },
-  }}
-/>
-```
-
-- Using `contentStyle: { backgroundColor: "transparent" }` makes the background liquid glass on iOS 26+.
-
-## Common route structure
-
-A standard app layout with tabs and stacks inside each tab:
-
-```
-app/
-  _layout.tsx — <NativeTabs />
-  (index,search)/
-    _layout.tsx — <Stack />
-    index.tsx — Main list
-    search.tsx — Search view
-```
-
-```tsx
-// app/_layout.tsx
-import { NativeTabs, Icon, Label } from "expo-router/unstable-native-tabs";
-import { Theme } from "../components/theme";
-
-export default function Layout() {
-  return (
-    <Theme>
-      <NativeTabs>
-        <NativeTabs.Trigger name="(index)">
-          <Icon sf="list.dash" />
-          <Label>Items</Label>
-        </NativeTabs.Trigger>
-        <NativeTabs.Trigger name="(search)" role="search" />
-      </NativeTabs>
-    </Theme>
-  );
-}
-```
-
-Create a shared group route so both tabs can push common screens:
-
-```tsx
-// app/(index,search)/_layout.tsx
-import { Stack } from "expo-router/stack";
-import { PlatformColor } from "react-native";
-
-export default function Layout({ segment }) {
-  const screen = segment.match(/\((.*)\)/)?.[1]!;
-  const titles: Record<string, string> = { index: "Items", search: "Search" };
-
-  return (
-    <Stack
-      screenOptions={{
-        headerTransparent: true,
-        headerShadowVisible: false,
-        headerLargeTitleShadowVisible: false,
-        headerLargeStyle: { backgroundColor: "transparent" },
-        headerTitleStyle: { color: PlatformColor("label") },
-        headerLargeTitle: true,
-        headerBlurEffect: "none",
-        headerBackButtonDisplayMode: "minimal",
-      }}
-    >
-      <Stack.Screen name={screen} options={{ title: titles[screen] }} />
-      <Stack.Screen name="i/[id]" options={{ headerLargeTitle: false }} />
-    </Stack>
-  );
-}
-```

+ 0 - 220
neo/.agents/skills/building-native-ui/references/animations.md

@@ -1,220 +0,0 @@
-# Animations
-
-Use Reanimated v4. Avoid React Native's built-in Animated API.
-
-## Entering and Exiting Animations
-
-Use Animated.View with entering and exiting animations. Layout animations can animate state changes.
-
-```tsx
-import Animated, {
-  FadeIn,
-  FadeOut,
-  LinearTransition,
-} from "react-native-reanimated";
-
-function App() {
-  return (
-    <Animated.View
-      entering={FadeIn}
-      exiting={FadeOut}
-      layout={LinearTransition}
-    />
-  );
-}
-```
-
-## On-Scroll Animations
-
-Create high-performance scroll animations using Reanimated's hooks:
-
-```tsx
-import Animated, {
-  useAnimatedRef,
-  useScrollViewOffset,
-  useAnimatedStyle,
-  interpolate,
-} from "react-native-reanimated";
-
-function Page() {
-  const ref = useAnimatedRef();
-  const scroll = useScrollViewOffset(ref);
-
-  const style = useAnimatedStyle(() => ({
-    opacity: interpolate(scroll.value, [0, 30], [0, 1], "clamp"),
-  }));
-
-  return (
-    <Animated.ScrollView ref={ref}>
-      <Animated.View style={style} />
-    </Animated.ScrollView>
-  );
-}
-```
-
-## Common Animation Presets
-
-### Entering Animations
-
-- `FadeIn`, `FadeInUp`, `FadeInDown`, `FadeInLeft`, `FadeInRight`
-- `SlideInUp`, `SlideInDown`, `SlideInLeft`, `SlideInRight`
-- `ZoomIn`, `ZoomInUp`, `ZoomInDown`
-- `BounceIn`, `BounceInUp`, `BounceInDown`
-
-### Exiting Animations
-
-- `FadeOut`, `FadeOutUp`, `FadeOutDown`, `FadeOutLeft`, `FadeOutRight`
-- `SlideOutUp`, `SlideOutDown`, `SlideOutLeft`, `SlideOutRight`
-- `ZoomOut`, `ZoomOutUp`, `ZoomOutDown`
-- `BounceOut`, `BounceOutUp`, `BounceOutDown`
-
-### Layout Animations
-
-- `LinearTransition` — Smooth linear interpolation
-- `SequencedTransition` — Sequenced property changes
-- `FadingTransition` — Fade between states
-
-## Customizing Animations
-
-```tsx
-<Animated.View
-  entering={FadeInDown.duration(500).delay(200)}
-  exiting={FadeOut.duration(300)}
-/>
-```
-
-### Modifiers
-
-```tsx
-// Duration in milliseconds
-FadeIn.duration(300);
-
-// Delay before starting
-FadeIn.delay(100);
-
-// Spring physics
-FadeIn.springify();
-FadeIn.springify().damping(15).stiffness(100);
-
-// Easing curves
-FadeIn.easing(Easing.bezier(0.25, 0.1, 0.25, 1));
-
-// Chaining
-FadeInDown.duration(400).delay(200).springify();
-```
-
-## Shared Value Animations
-
-For imperative control over animations:
-
-```tsx
-import {
-  useSharedValue,
-  withSpring,
-  withTiming,
-} from "react-native-reanimated";
-
-const offset = useSharedValue(0);
-
-// Spring animation
-offset.value = withSpring(100);
-
-// Timing animation
-offset.value = withTiming(100, { duration: 300 });
-
-// Use in styles
-const style = useAnimatedStyle(() => ({
-  transform: [{ translateX: offset.value }],
-}));
-```
-
-## Gesture Animations
-
-Combine with React Native Gesture Handler:
-
-```tsx
-import { Gesture, GestureDetector } from "react-native-gesture-handler";
-import Animated, {
-  useSharedValue,
-  useAnimatedStyle,
-  withSpring,
-} from "react-native-reanimated";
-
-function DraggableBox() {
-  const translateX = useSharedValue(0);
-  const translateY = useSharedValue(0);
-
-  const gesture = Gesture.Pan()
-    .onUpdate((e) => {
-      translateX.value = e.translationX;
-      translateY.value = e.translationY;
-    })
-    .onEnd(() => {
-      translateX.value = withSpring(0);
-      translateY.value = withSpring(0);
-    });
-
-  const style = useAnimatedStyle(() => ({
-    transform: [
-      { translateX: translateX.value },
-      { translateY: translateY.value },
-    ],
-  }));
-
-  return (
-    <GestureDetector gesture={gesture}>
-      <Animated.View style={[styles.box, style]} />
-    </GestureDetector>
-  );
-}
-```
-
-## Keyboard Animations
-
-Animate with keyboard height changes:
-
-```tsx
-import Animated, {
-  useAnimatedKeyboard,
-  useAnimatedStyle,
-} from "react-native-reanimated";
-
-function KeyboardAwareView() {
-  const keyboard = useAnimatedKeyboard();
-
-  const style = useAnimatedStyle(() => ({
-    paddingBottom: keyboard.height.value,
-  }));
-
-  return <Animated.View style={style}>{/* content */}</Animated.View>;
-}
-```
-
-## Staggered List Animations
-
-Animate list items with delays:
-
-```tsx
-{
-  items.map((item, index) => (
-    <Animated.View
-      key={item.id}
-      entering={FadeInUp.delay(index * 50)}
-      exiting={FadeOutUp}
-    >
-      <ListItem item={item} />
-    </Animated.View>
-  ));
-}
-```
-
-## Best Practices
-
-- Add entering and exiting animations for state changes
-- Use layout animations when items are added/removed from lists
-- Use `useAnimatedStyle` for scroll-driven animations
-- Prefer `interpolate` with "clamp" for bounded values
-- You can't pass PlatformColors to reanimated views or styles; use static colors instead
-- Keep animations under 300ms for responsive feel
-- Use spring animations for natural movement
-- Avoid animating layout properties (width, height) when possible — prefer transforms

+ 0 - 270
neo/.agents/skills/building-native-ui/references/controls.md

@@ -1,270 +0,0 @@
-# Native Controls
-
-Native iOS controls provide built-in haptics, accessibility, and platform-appropriate styling.
-
-## Switch
-
-Use for binary on/off settings. Has built-in haptics.
-
-```tsx
-import { Switch } from "react-native";
-import { useState } from "react";
-
-const [enabled, setEnabled] = useState(false);
-
-<Switch value={enabled} onValueChange={setEnabled} />;
-```
-
-### Customization
-
-```tsx
-<Switch
-  value={enabled}
-  onValueChange={setEnabled}
-  trackColor={{ false: "#767577", true: "#81b0ff" }}
-  thumbColor={enabled ? "#f5dd4b" : "#f4f3f4"}
-  ios_backgroundColor="#3e3e3e"
-/>
-```
-
-## Segmented Control
-
-Use for non-navigational tabs or mode selection. Avoid changing default colors.
-
-```tsx
-import SegmentedControl from "@react-native-segmented-control/segmented-control";
-import { useState } from "react";
-
-const [index, setIndex] = useState(0);
-
-<SegmentedControl
-  values={["All", "Active", "Done"]}
-  selectedIndex={index}
-  onChange={({ nativeEvent }) => setIndex(nativeEvent.selectedSegmentIndex)}
-/>;
-```
-
-### Rules
-
-- Maximum 4 options — use a picker for more
-- Keep labels short (1-2 words)
-- Avoid custom colors — native styling adapts to dark mode
-
-### With Icons (iOS 14+)
-
-```tsx
-<SegmentedControl
-  values={[
-    { label: "List", icon: "list.bullet" },
-    { label: "Grid", icon: "square.grid.2x2" },
-  ]}
-  selectedIndex={index}
-  onChange={({ nativeEvent }) => setIndex(nativeEvent.selectedSegmentIndex)}
-/>
-```
-
-## Slider
-
-Continuous value selection.
-
-```tsx
-import Slider from "@react-native-community/slider";
-import { useState } from "react";
-
-const [value, setValue] = useState(0.5);
-
-<Slider
-  value={value}
-  onValueChange={setValue}
-  minimumValue={0}
-  maximumValue={1}
-/>;
-```
-
-### Customization
-
-```tsx
-<Slider
-  value={value}
-  onValueChange={setValue}
-  minimumValue={0}
-  maximumValue={100}
-  step={1}
-  minimumTrackTintColor="#007AFF"
-  maximumTrackTintColor="#E5E5EA"
-  thumbTintColor="#007AFF"
-/>
-```
-
-### Discrete Steps
-
-```tsx
-<Slider
-  value={value}
-  onValueChange={setValue}
-  minimumValue={0}
-  maximumValue={10}
-  step={1}
-/>
-```
-
-## Date/Time Picker
-
-Compact pickers with popovers. Has built-in haptics.
-
-```tsx
-import DateTimePicker from "@react-native-community/datetimepicker";
-import { useState } from "react";
-
-const [date, setDate] = useState(new Date());
-
-<DateTimePicker
-  value={date}
-  onChange={(event, selectedDate) => {
-    if (selectedDate) setDate(selectedDate);
-  }}
-  mode="datetime"
-/>;
-```
-
-### Modes
-
-- `date` — Date only
-- `time` — Time only
-- `datetime` — Date and time
-
-### Display Styles
-
-```tsx
-// Compact inline (default)
-<DateTimePicker value={date} mode="date" />
-
-// Spinner wheel
-<DateTimePicker
-  value={date}
-  mode="date"
-  display="spinner"
-  style={{ width: 200, height: 150 }}
-/>
-
-// Full calendar
-<DateTimePicker value={date} mode="date" display="inline" />
-```
-
-### Time Intervals
-
-```tsx
-<DateTimePicker
-  value={date}
-  mode="time"
-  minuteInterval={15}
-/>
-```
-
-### Min/Max Dates
-
-```tsx
-<DateTimePicker
-  value={date}
-  mode="date"
-  minimumDate={new Date(2020, 0, 1)}
-  maximumDate={new Date(2030, 11, 31)}
-/>
-```
-
-## Stepper
-
-Increment/decrement numeric values.
-
-```tsx
-import { Stepper } from "react-native";
-import { useState } from "react";
-
-const [count, setCount] = useState(0);
-
-<Stepper
-  value={count}
-  onValueChange={setCount}
-  minimumValue={0}
-  maximumValue={10}
-/>;
-```
-
-## TextInput
-
-Native text input with various keyboard types.
-
-```tsx
-import { TextInput } from "react-native";
-
-<TextInput
-  placeholder="Enter text..."
-  placeholderTextColor="#999"
-  style={{
-    padding: 12,
-    fontSize: 16,
-    borderRadius: 8,
-    backgroundColor: "#f0f0f0",
-  }}
-/>
-```
-
-### Keyboard Types
-
-```tsx
-// Email
-<TextInput keyboardType="email-address" autoCapitalize="none" />
-
-// Phone
-<TextInput keyboardType="phone-pad" />
-
-// Number
-<TextInput keyboardType="numeric" />
-
-// Password
-<TextInput secureTextEntry />
-
-// Search
-<TextInput
-  returnKeyType="search"
-  enablesReturnKeyAutomatically
-/>
-```
-
-### Multiline
-
-```tsx
-<TextInput
-  multiline
-  numberOfLines={4}
-  textAlignVertical="top"
-  style={{ minHeight: 100 }}
-/>
-```
-
-## Picker (Wheel)
-
-For selection from many options (5+ items).
-
-```tsx
-import { Picker } from "@react-native-picker/picker";
-import { useState } from "react";
-
-const [selected, setSelected] = useState("js");
-
-<Picker selectedValue={selected} onValueChange={setSelected}>
-  <Picker.Item label="JavaScript" value="js" />
-  <Picker.Item label="TypeScript" value="ts" />
-  <Picker.Item label="Python" value="py" />
-  <Picker.Item label="Go" value="go" />
-</Picker>;
-```
-
-## Best Practices
-
-- **Haptics**: Switch and DateTimePicker have built-in haptics — don't add extra
-- **Accessibility**: Native controls have proper accessibility labels by default
-- **Dark Mode**: Avoid custom colors — native styling adapts automatically
-- **Spacing**: Use consistent padding around controls (12-16pt)
-- **Labels**: Place labels above or to the left of controls
-- **Grouping**: Group related controls in sections with headers

+ 0 - 253
neo/.agents/skills/building-native-ui/references/form-sheet.md

@@ -1,253 +0,0 @@
-# Form Sheets in Expo Router
-
-This skill covers implementing form sheets with footers using Expo Router's Stack navigator and react-native-screens.
-
-## Overview
-
-Form sheets are modal presentations that appear as a card sliding up from the bottom of the screen. They're ideal for:
-
-- Quick actions and confirmations
-- Settings panels
-- Login/signup flows
-- Action sheets with custom content
-
-**Requirements:**
-
-- Expo Router Stack navigator
-
-## Basic Usage
-
-### Form Sheet with Footer
-
-Configure the Stack.Screen with transparent backgrounds and sheet presentation:
-
-```tsx
-// app/_layout.tsx
-import { Stack } from "expo-router";
-
-export default function Layout() {
-  return (
-    <Stack>
-      <Stack.Screen name="index" />
-      <Stack.Screen
-        name="about"
-        options={{
-          presentation: "formSheet",
-          sheetAllowedDetents: [0.25],
-          headerTransparent: true,
-          contentStyle: { backgroundColor: "transparent" },
-          sheetGrabberVisible: true,
-        }}
-      >
-        <Stack.Header style={{ backgroundColor: "transparent" }}></Stack.Header>
-      </Stack.Screen>
-    </Stack>
-  );
-}
-```
-
-### Form Sheet Screen Content
-
-> Requires Expo SDK 55 or later.
-
-Use `flex: 1` to allow the content to fill available space, enabling footer positioning:
-
-```tsx
-// app/about.tsx
-import { View, Text, StyleSheet } from "react-native";
-
-export default function AboutSheet() {
-  return (
-    <View style={styles.container}>
-      {/* Main content */}
-      <View style={styles.content}>
-        <Text>Sheet Content</Text>
-      </View>
-
-      {/* Footer - stays at bottom */}
-      <View style={styles.footer}>
-        <Text>Footer Content</Text>
-      </View>
-    </View>
-  );
-}
-
-const styles = StyleSheet.create({
-  container: {
-    flex: 1,
-  },
-  content: {
-    flex: 1,
-    padding: 16,
-  },
-  footer: {
-    padding: 16,
-  },
-});
-```
-
-### Formsheet with interactive content below
-
-Use `sheetLargestUndimmedDetentIndex` (zero-indexed) to keep content behind the form sheet interactive — e.g. letting users pan a map beneath it. Setting it to `1` allows interaction at the first two detents but dims on the third.
-
-```tsx
-// app/_layout.tsx
-import { Stack } from 'expo-router';
-
-export default function Layout() {
-  return (
-    <Stack screenOptions={{ headerShown: false }}>
-      <Stack.Screen name="index" />
-      <Stack.Screen
-        name="info-sheet"
-        options={{
-          presentation: "formSheet",
-          sheetAllowedDetents: [0.2, 0.5, 1.0],
-          sheetLargestUndimmedDetentIndex: 1,
-          /* other options */
-        }}
-      />
-    </Stack>
-  )
-}
-```
-
-## Key Options
-
-| Option                | Type       | Description                                                 |
-| --------------------- | ---------- | ----------------------------------------------------------- |
-| `presentation`        | `string`   | Set to `'formSheet'` for sheet presentation                 |
-| `sheetGrabberVisible` | `boolean`  | Shows the drag handle at the top of the sheet               |
-| `sheetAllowedDetents` | `number[]` | Array of detent heights (0-1 range, e.g., `[0.25]` for 25%) |
-| `headerTransparent`   | `boolean`  | Makes header background transparent                         |
-| `contentStyle`        | `object`   | Style object for the screen content container               |
-| `title`               | `string`   | Screen title (set to `''` for no title)                     |
-
-## Common Detent Values
-
-- `[0.25]` - Quarter sheet (compact actions)
-- `[0.5]` - Half sheet (medium content)
-- `[0.75]` - Three-quarter sheet (detailed forms)
-- `[0.25, 0.5, 1]` - Multiple stops (expandable sheet)
-
-## Complete Example
-
-```tsx
-// _layout.tsx
-import { Stack } from "expo-router";
-
-export default function Layout() {
-  return (
-    <Stack>
-      <Stack.Screen name="index" options={{ title: "Home" }} />
-      <Stack.Screen
-        name="confirm"
-        options={{
-          contentStyle: { backgroundColor: "transparent" },
-          presentation: "formSheet",
-          title: "",
-          sheetGrabberVisible: true,
-          sheetAllowedDetents: [0.25],
-          headerTransparent: true,
-        }}
-      >
-        <Stack.Header style={{ backgroundColor: "transparent" }}>
-          <Stack.Header.Right />
-        </Stack.Header>
-      </Stack.Screen>
-    </Stack>
-  );
-}
-```
-
-```tsx
-// app/confirm.tsx
-import { View, Text, Pressable, StyleSheet } from "react-native";
-import { router } from "expo-router";
-
-export default function ConfirmSheet() {
-  return (
-    <View style={styles.container}>
-      <View style={styles.content}>
-        <Text style={styles.title}>Confirm Action</Text>
-        <Text style={styles.description}>
-          Are you sure you want to proceed?
-        </Text>
-      </View>
-
-      <View style={styles.footer}>
-        <Pressable style={styles.cancelButton} onPress={() => router.back()}>
-          <Text style={styles.cancelText}>Cancel</Text>
-        </Pressable>
-        <Pressable style={styles.confirmButton} onPress={() => router.back()}>
-          <Text style={styles.confirmText}>Confirm</Text>
-        </Pressable>
-      </View>
-    </View>
-  );
-}
-
-const styles = StyleSheet.create({
-  container: {
-    flex: 1,
-  },
-  content: {
-    flex: 1,
-    padding: 20,
-    alignItems: "center",
-    justifyContent: "center",
-  },
-  title: {
-    fontSize: 18,
-    fontWeight: "600",
-    marginBottom: 8,
-  },
-  description: {
-    fontSize: 14,
-    color: "#666",
-    textAlign: "center",
-  },
-  footer: {
-    flexDirection: "row",
-    padding: 16,
-    gap: 12,
-  },
-  cancelButton: {
-    flex: 1,
-    padding: 14,
-    borderRadius: 10,
-    backgroundColor: "#f0f0f0",
-    alignItems: "center",
-  },
-  cancelText: {
-    fontSize: 16,
-    fontWeight: "500",
-  },
-  confirmButton: {
-    flex: 1,
-    padding: 14,
-    borderRadius: 10,
-    backgroundColor: "#007AFF",
-    alignItems: "center",
-  },
-  confirmText: {
-    fontSize: 16,
-    fontWeight: "500",
-    color: "white",
-  },
-});
-```
-
-## Troubleshooting
-
-### Content not filling sheet
-
-Make sure the root View uses `flex: 1`:
-
-```tsx
-<View style={{ flex: 1 }}>{/* content */}</View>
-```
-
-### Sheet background showing through
-
-Set `contentStyle: { backgroundColor: 'transparent' }` in options and style your content container with the desired background color instead.

+ 0 - 106
neo/.agents/skills/building-native-ui/references/gradients.md

@@ -1,106 +0,0 @@
-# CSS Gradients
-
-> **New Architecture Only**: CSS gradients require React Native's New Architecture (Fabric). They are not available in the old architecture or Expo Go.
-
-Use CSS gradients with the `experimental_backgroundImage` style property.
-
-## Linear Gradients
-
-```tsx
-// Top to bottom
-<View style={{
-  experimental_backgroundImage: 'linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 100%)'
-}} />
-
-// Left to right
-<View style={{
-  experimental_backgroundImage: 'linear-gradient(to right, #ff0000 0%, #0000ff 100%)'
-}} />
-
-// Diagonal
-<View style={{
-  experimental_backgroundImage: 'linear-gradient(45deg, #ff0000 0%, #00ff00 50%, #0000ff 100%)'
-}} />
-
-// Using degrees
-<View style={{
-  experimental_backgroundImage: 'linear-gradient(135deg, transparent 0%, black 100%)'
-}} />
-```
-
-## Radial Gradients
-
-```tsx
-// Circle at center
-<View style={{
-  experimental_backgroundImage: 'radial-gradient(circle at center, rgba(255, 0, 0, 1) 0%, rgba(0, 0, 255, 1) 100%)'
-}} />
-
-// Ellipse
-<View style={{
-  experimental_backgroundImage: 'radial-gradient(ellipse at center, #fff 0%, #000 100%)'
-}} />
-
-// Positioned
-<View style={{
-  experimental_backgroundImage: 'radial-gradient(circle at top left, #ff0000 0%, transparent 70%)'
-}} />
-```
-
-## Multiple Gradients
-
-Stack multiple gradients by comma-separating them:
-
-```tsx
-<View style={{
-  experimental_backgroundImage: `
-    linear-gradient(to bottom, transparent 0%, black 100%),
-    radial-gradient(circle at top right, rgba(255, 0, 0, 0.5) 0%, transparent 50%)
-  `
-}} />
-```
-
-## Common Patterns
-
-### Overlay on Image
-
-```tsx
-<View style={{ position: 'relative' }}>
-  <Image source={{ uri: '...' }} style={{ width: '100%', height: 200 }} />
-  <View style={{
-    position: 'absolute',
-    inset: 0,
-    experimental_backgroundImage: 'linear-gradient(to top, rgba(0, 0, 0, 0.8) 0%, transparent 50%)'
-  }} />
-</View>
-```
-
-### Frosted Glass Effect
-
-```tsx
-<View style={{
-  experimental_backgroundImage: 'linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%)',
-  backdropFilter: 'blur(10px)',
-}} />
-```
-
-### Button Gradient
-
-```tsx
-<Pressable style={{
-  experimental_backgroundImage: 'linear-gradient(to bottom, #4CAF50 0%, #388E3C 100%)',
-  padding: 16,
-  borderRadius: 8,
-}}>
-  <Text style={{ color: 'white', textAlign: 'center' }}>Submit</Text>
-</Pressable>
-```
-
-## Important Notes
-
-- Do NOT use `expo-linear-gradient` — use CSS gradients instead
-- Gradients are strings, not objects
-- Use `rgba()` for transparency, or `transparent` keyword
-- Color stops use percentages (0%, 50%, 100%)
-- Direction keywords: `to top`, `to bottom`, `to left`, `to right`, `to top left`, etc.
-- Degree values: `45deg`, `90deg`, `135deg`, etc.

+ 0 - 213
neo/.agents/skills/building-native-ui/references/icons.md

@@ -1,213 +0,0 @@
-# Icons (SF Symbols)
-
-Use SF Symbols for native feel. Never use FontAwesome or Ionicons.
-
-## Basic Usage
-
-```tsx
-import { SymbolView } from "expo-symbols";
-import { PlatformColor } from "react-native";
-
-<SymbolView
-  tintColor={PlatformColor("label")}
-  resizeMode="scaleAspectFit"
-  name="square.and.arrow.down"
-  style={{ width: 16, height: 16 }}
-/>;
-```
-
-## Props
-
-```tsx
-<SymbolView
-  name="star.fill"                    // SF Symbol name (required)
-  tintColor={PlatformColor("label")}  // Icon color
-  size={24}                           // Shorthand for width/height
-  resizeMode="scaleAspectFit"         // How to scale
-  weight="regular"                    // thin | ultraLight | light | regular | medium | semibold | bold | heavy | black
-  scale="medium"                      // small | medium | large
-  style={{ width: 16, height: 16 }}   // Standard style props
-/>
-```
-
-## Common Icons
-
-### Navigation & Actions
-- `house.fill` - home
-- `gear` - settings
-- `magnifyingglass` - search
-- `plus` - add
-- `xmark` - close
-- `chevron.left` - back
-- `chevron.right` - forward
-- `arrow.left` - back arrow
-- `arrow.right` - forward arrow
-
-### Media
-- `play.fill` - play
-- `pause.fill` - pause
-- `stop.fill` - stop
-- `backward.fill` - rewind
-- `forward.fill` - fast forward
-- `speaker.wave.2.fill` - volume
-- `speaker.slash.fill` - mute
-
-### Camera
-- `camera` - camera
-- `camera.fill` - camera filled
-- `arrow.triangle.2.circlepath` - flip camera
-- `photo` - gallery/photos
-- `bolt` - flash
-- `bolt.slash` - flash off
-
-### Communication
-- `message` - message
-- `message.fill` - message filled
-- `envelope` - email
-- `envelope.fill` - email filled
-- `phone` - phone
-- `phone.fill` - phone filled
-- `video` - video call
-- `video.fill` - video call filled
-
-### Social
-- `heart` - like
-- `heart.fill` - liked
-- `star` - favorite
-- `star.fill` - favorited
-- `hand.thumbsup` - thumbs up
-- `hand.thumbsdown` - thumbs down
-- `person` - profile
-- `person.fill` - profile filled
-- `person.2` - people
-- `person.2.fill` - people filled
-
-### Content Actions
-- `square.and.arrow.up` - share
-- `square.and.arrow.down` - download
-- `doc.on.doc` - copy
-- `trash` - delete
-- `pencil` - edit
-- `folder` - folder
-- `folder.fill` - folder filled
-- `bookmark` - bookmark
-- `bookmark.fill` - bookmarked
-
-### Status & Feedback
-- `checkmark` - success/done
-- `checkmark.circle.fill` - completed
-- `xmark.circle.fill` - error/failed
-- `exclamationmark.triangle` - warning
-- `info.circle` - info
-- `questionmark.circle` - help
-- `bell` - notification
-- `bell.fill` - notification filled
-
-### Misc
-- `ellipsis` - more options
-- `ellipsis.circle` - more in circle
-- `line.3.horizontal` - menu/hamburger
-- `slider.horizontal.3` - filters
-- `arrow.clockwise` - refresh
-- `location` - location
-- `location.fill` - location filled
-- `map` - map
-- `mappin` - pin
-- `clock` - time
-- `calendar` - calendar
-- `link` - link
-- `nosign` - block/prohibited
-
-## Animated Symbols
-
-```tsx
-<SymbolView
-  name="checkmark.circle"
-  animationSpec={{
-    effect: {
-      type: "bounce",
-      direction: "up",
-    },
-  }}
-/>
-```
-
-### Animation Effects
-
-- `bounce` - Bouncy animation
-- `pulse` - Pulsing effect
-- `variableColor` - Color cycling
-- `scale` - Scale animation
-
-```tsx
-// Bounce with direction
-animationSpec={{
-  effect: { type: "bounce", direction: "up" }  // up | down
-}}
-
-// Pulse
-animationSpec={{
-  effect: { type: "pulse" }
-}}
-
-// Variable color (multicolor symbols)
-animationSpec={{
-  effect: {
-    type: "variableColor",
-    cumulative: true,
-    reversing: true
-  }
-}}
-```
-
-## Symbol Weights
-
-```tsx
-// Lighter weights
-<SymbolView name="star" weight="ultraLight" />
-<SymbolView name="star" weight="thin" />
-<SymbolView name="star" weight="light" />
-
-// Default
-<SymbolView name="star" weight="regular" />
-
-// Heavier weights
-<SymbolView name="star" weight="medium" />
-<SymbolView name="star" weight="semibold" />
-<SymbolView name="star" weight="bold" />
-<SymbolView name="star" weight="heavy" />
-<SymbolView name="star" weight="black" />
-```
-
-## Symbol Scales
-
-```tsx
-<SymbolView name="star" scale="small" />
-<SymbolView name="star" scale="medium" />  // default
-<SymbolView name="star" scale="large" />
-```
-
-## Multicolor Symbols
-
-Some symbols support multiple colors:
-
-```tsx
-<SymbolView
-  name="cloud.sun.rain.fill"
-  type="multicolor"
-/>
-```
-
-## Finding Symbol Names
-
-1. Use the SF Symbols app on macOS (free from Apple)
-2. Search at https://developer.apple.com/sf-symbols/
-3. Symbol names use dot notation: `square.and.arrow.up`
-
-## Best Practices
-
-- Always use SF Symbols over vector icon libraries
-- Match symbol weight to nearby text weight
-- Use `.fill` variants for selected/active states
-- Use PlatformColor for tint to support dark mode
-- Keep icons at consistent sizes (16, 20, 24, 32)

+ 0 - 198
neo/.agents/skills/building-native-ui/references/media.md

@@ -1,198 +0,0 @@
-# Media
-
-## Camera
-
-- Hide navigation headers when there's a full screen camera
-- Ensure to flip the camera with `mirror` to emulate social apps
-- Use liquid glass buttons on cameras
-- Icons: `arrow.triangle.2.circlepath` (flip), `photo` (gallery), `bolt` (flash)
-- Eagerly request camera permission
-- Lazily request media library permission
-
-```tsx
-import React, { useRef, useState } from "react";
-import { View, TouchableOpacity, Text, Alert } from "react-native";
-import { CameraView, CameraType, useCameraPermissions } from "expo-camera";
-import * as MediaLibrary from "expo-media-library";
-import * as ImagePicker from "expo-image-picker";
-import * as Haptics from "expo-haptics";
-import { SymbolView } from "expo-symbols";
-import { PlatformColor } from "react-native";
-import { GlassView } from "expo-glass-effect";
-import { useSafeAreaInsets } from "react-native-safe-area-context";
-
-function Camera({ onPicture }: { onPicture: (uri: string) => Promise<void> }) {
-  const [permission, requestPermission] = useCameraPermissions();
-  const cameraRef = useRef<CameraView>(null);
-  const [type, setType] = useState<CameraType>("back");
-  const { bottom } = useSafeAreaInsets();
-
-  if (!permission?.granted) {
-    return (
-      <View style={{ flex: 1, justifyContent: "center", alignItems: "center", backgroundColor: PlatformColor("systemBackground") }}>
-        <Text style={{ color: PlatformColor("label"), padding: 16 }}>Camera access is required</Text>
-        <GlassView isInteractive tintColor={PlatformColor("systemBlue")} style={{ borderRadius: 12 }}>
-          <TouchableOpacity onPress={requestPermission} style={{ padding: 12, borderRadius: 12 }}>
-            <Text style={{ color: "white" }}>Grant Permission</Text>
-          </TouchableOpacity>
-        </GlassView>
-      </View>
-    );
-  }
-
-  const takePhoto = async () => {
-    await Haptics.selectionAsync();
-    if (!cameraRef.current) return;
-    const photo = await cameraRef.current.takePictureAsync({ quality: 0.8 });
-    await onPicture(photo.uri);
-  };
-
-  const selectPhoto = async () => {
-    await Haptics.selectionAsync();
-    const result = await ImagePicker.launchImageLibraryAsync({
-      mediaTypes: "images",
-      allowsEditing: false,
-      quality: 0.8,
-    });
-    if (!result.canceled && result.assets?.[0]) {
-      await onPicture(result.assets[0].uri);
-    }
-  };
-
-  return (
-    <View style={{ flex: 1, backgroundColor: "black" }}>
-      <CameraView ref={cameraRef} mirror style={{ flex: 1 }} facing={type} />
-      <View style={{ position: "absolute", left: 0, right: 0, bottom: bottom, gap: 16, alignItems: "center" }}>
-        <GlassView isInteractive style={{ padding: 8, borderRadius: 99 }}>
-          <TouchableOpacity onPress={takePhoto} style={{ width: 64, height: 64, borderRadius: 99, backgroundColor: "white" }} />
-        </GlassView>
-        <View style={{ flexDirection: "row", justifyContent: "space-around", paddingHorizontal: 8 }}>
-          <GlassButton onPress={selectPhoto} icon="photo" />
-          <GlassButton onPress={() => setType(t => t === "back" ? "front" : "back")} icon="arrow.triangle.2.circlepath" />
-        </View>
-      </View>
-    </View>
-  );
-}
-```
-
-## Audio Playback
-
-Use `expo-audio` not `expo-av`:
-
-```tsx
-import { useAudioPlayer } from 'expo-audio';
-
-const player = useAudioPlayer({ uri: 'https://stream.nightride.fm/rektory.mp3' });
-
-<Button title="Play" onPress={() => player.play()} />
-```
-
-## Audio Recording (Microphone)
-
-```tsx
-import {
-  useAudioRecorder,
-  AudioModule,
-  RecordingPresets,
-  setAudioModeAsync,
-  useAudioRecorderState,
-} from 'expo-audio';
-import { useEffect } from 'react';
-import { Alert, Button } from 'react-native';
-
-function App() {
-  const audioRecorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY);
-  const recorderState = useAudioRecorderState(audioRecorder);
-
-  const record = async () => {
-    await audioRecorder.prepareToRecordAsync();
-    audioRecorder.record();
-  };
-
-  const stop = () => audioRecorder.stop();
-
-  useEffect(() => {
-    (async () => {
-      const status = await AudioModule.requestRecordingPermissionsAsync();
-      if (status.granted) {
-        setAudioModeAsync({ playsInSilentMode: true, allowsRecording: true });
-      } else {
-        Alert.alert('Permission to access microphone was denied');
-      }
-    })();
-  }, []);
-
-  return (
-    <Button
-      title={recorderState.isRecording ? 'Stop' : 'Start'}
-      onPress={recorderState.isRecording ? stop : record}
-    />
-  );
-}
-```
-
-## Video Playback
-
-Use `expo-video` not `expo-av`:
-
-```tsx
-import { useVideoPlayer, VideoView } from 'expo-video';
-import { useEvent } from 'expo';
-
-const videoSource = 'https://example.com/video.mp4';
-
-const player = useVideoPlayer(videoSource, player => {
-  player.loop = true;
-  player.play();
-});
-
-const { isPlaying } = useEvent(player, 'playingChange', { isPlaying: player.playing });
-
-<VideoView player={player} fullscreenOptions={{}} allowsPictureInPicture />
-```
-
-VideoView options:
-- `allowsPictureInPicture`: boolean
-- `contentFit`: 'contain' | 'cover' | 'fill'
-- `nativeControls`: boolean
-- `playsInline`: boolean
-- `startsPictureInPictureAutomatically`: boolean
-
-## Saving Media
-
-```tsx
-import * as MediaLibrary from "expo-media-library";
-
-const { granted } = await MediaLibrary.requestPermissionsAsync();
-if (granted) {
-  await MediaLibrary.saveToLibraryAsync(uri);
-}
-```
-
-### Saving Base64 Images
-
-`MediaLibrary.saveToLibraryAsync` only accepts local file paths. Save base64 strings to disk first:
-
-```tsx
-import { File, Paths } from "expo-file-system/next";
-
-function base64ToLocalUri(base64: string, filename?: string) {
-  if (!filename) {
-    const match = base64.match(/^data:(image\/[a-zA-Z]+);base64,/);
-    const ext = match ? match[1].split("/")[1] : "jpg";
-    filename = `generated-${Date.now()}.${ext}`;
-  }
-
-  if (base64.startsWith("data:")) base64 = base64.split(",")[1];
-  const binaryString = atob(base64);
-  const len = binaryString.length;
-  const bytes = new Uint8Array(new ArrayBuffer(len));
-  for (let i = 0; i < len; i++) bytes[i] = binaryString.charCodeAt(i);
-
-  const f = new File(Paths.cache, filename);
-  f.create({ overwrite: true });
-  f.write(bytes);
-  return f.uri;
-}
-```

+ 0 - 229
neo/.agents/skills/building-native-ui/references/route-structure.md

@@ -1,229 +0,0 @@
-# Route Structure
-
-## File Conventions
-
-- Routes belong in the `app` directory
-- Use `[]` for dynamic routes, e.g. `[id].tsx`
-- Routes can never be named `(foo).tsx` - use `(foo)/index.tsx` instead
-- Use `(group)` routes to simplify the public URL structure
-- NEVER co-locate components, types, or utilities in the app directory - these should be in separate directories like `components/`, `utils/`, etc.
-- The app directory should only contain route and `_layout` files; every file should export a default component
-- Ensure the app always has a route that matches "/" so the app is never blank
-- ALWAYS use `_layout.tsx` files to define stacks
-
-## Dynamic Routes
-
-Use square brackets for dynamic segments:
-
-```
-app/
-  users/
-    [id].tsx        # Matches /users/123, /users/abc
-    [id]/
-      posts.tsx     # Matches /users/123/posts
-```
-
-### Catch-All Routes
-
-Use `[...slug]` for catch-all routes:
-
-```
-app/
-  docs/
-    [...slug].tsx   # Matches /docs/a, /docs/a/b, /docs/a/b/c
-```
-
-## Query Parameters
-
-Access query parameters with the `useLocalSearchParams` hook:
-
-```tsx
-import { useLocalSearchParams } from "expo-router";
-
-function Page() {
-  const { id } = useLocalSearchParams<{ id: string }>();
-}
-```
-
-For dynamic routes, the parameter name matches the file name:
-
-- `[id].tsx` → `useLocalSearchParams<{ id: string }>()`
-- `[slug].tsx` → `useLocalSearchParams<{ slug: string }>()`
-
-## Pathname
-
-Access the current pathname with the `usePathname` hook:
-
-```tsx
-import { usePathname } from "expo-router";
-
-function Component() {
-  const pathname = usePathname(); // e.g. "/users/123"
-}
-```
-
-## Group Routes
-
-Use parentheses for groups that don't affect the URL:
-
-```
-app/
-  (auth)/
-    login.tsx       # URL: /login
-    register.tsx    # URL: /register
-  (main)/
-    index.tsx       # URL: /
-    settings.tsx    # URL: /settings
-```
-
-Groups are useful for:
-
-- Organizing related routes
-- Applying different layouts to route groups
-- Keeping URLs clean
-
-## Stacks and Tabs Structure
-
-When an app has tabs, the header and title should be set in a Stack that is nested INSIDE each tab. This allows tabs to have their own headers and distinct histories. The root layout should often not have a header.
-
-- Set the 'headerShown' option to false on the tab layout
-- Use (group) routes to simplify the public URL structure
-- You may need to delete or refactor existing routes to fit this structure
-
-Example structure:
-
-```
-app/
-  _layout.tsx — <Tabs />
-  (home)/
-    _layout.tsx — <Stack />
-    index.tsx — <ScrollView />
-  (settings)/
-    _layout.tsx — <Stack />
-    index.tsx — <ScrollView />
-  (home,settings)/
-    info.tsx — <ScrollView /> (shared across tabs)
-```
-
-## Array Routes for Multiple Stacks
-
-Use array routes '(index,settings)' to create multiple stacks. This is useful for tabs that need to share screens across stacks.
-
-```
-app/
-  _layout.tsx — <Tabs />
-  (index,settings)/
-    _layout.tsx — <Stack />
-    index.tsx — <ScrollView />
-    settings.tsx — <ScrollView />
-```
-
-This requires a specialized layout with explicit anchor routes:
-
-```tsx
-// app/(index,settings)/_layout.tsx
-import { useMemo } from "react";
-import Stack from "expo-router/stack";
-
-export const unstable_settings = {
-  index: { anchor: "index" },
-  settings: { anchor: "settings" },
-};
-
-export default function Layout({ segment }: { segment: string }) {
-  const screen = segment.match(/\((.*)\)/)?.[1]!;
-
-  const options = useMemo(() => {
-    switch (screen) {
-      case "index":
-        return { headerRight: () => <></> };
-      default:
-        return {};
-    }
-  }, [screen]);
-
-  return (
-    <Stack>
-      <Stack.Screen name={screen} options={options} />
-    </Stack>
-  );
-}
-```
-
-## Complete App Structure Example
-
-```
-app/
-  _layout.tsx — <NativeTabs />
-  (index,search)/
-    _layout.tsx — <Stack />
-    index.tsx — Main list
-    search.tsx — Search view
-    i/[id].tsx — Detail page
-components/
-  theme.tsx
-  list.tsx
-utils/
-  storage.ts
-  use-search.ts
-```
-
-## Layout Files
-
-Every directory can have a `_layout.tsx` file that wraps all routes in that directory:
-
-```tsx
-// app/_layout.tsx
-import { Stack } from "expo-router/stack";
-
-export default function RootLayout() {
-  return <Stack />;
-}
-```
-
-```tsx
-// app/(tabs)/_layout.tsx
-import { NativeTabs, Icon, Label } from "expo-router/unstable-native-tabs";
-
-export default function TabLayout() {
-  return (
-    <NativeTabs>
-      <NativeTabs.Trigger name="index">
-        <Label>Home</Label>
-        <Icon sf="house.fill" />
-      </NativeTabs.Trigger>
-    </NativeTabs>
-  );
-}
-```
-
-## Route Settings
-
-Export `unstable_settings` to configure route behavior:
-
-```tsx
-export const unstable_settings = {
-  anchor: "index",
-};
-```
-
-- `initialRouteName` was renamed to `anchor` in v4
-
-## Not Found Routes
-
-Create a `+not-found.tsx` file to handle unmatched routes:
-
-```tsx
-// app/+not-found.tsx
-import { Link } from "expo-router";
-import { View, Text } from "react-native";
-
-export default function NotFound() {
-  return (
-    <View>
-      <Text>Page not found</Text>
-      <Link href="/">Go home</Link>
-    </View>
-  );
-}
-```

+ 0 - 248
neo/.agents/skills/building-native-ui/references/search.md

@@ -1,248 +0,0 @@
-# Search
-
-## Header Search Bar
-
-Add a search bar to the stack header with `headerSearchBarOptions`:
-
-```tsx
-<Stack.Screen
-  name="index"
-  options={{
-    headerSearchBarOptions: {
-      placeholder: "Search",
-      onChangeText: (event) => console.log(event.nativeEvent.text),
-    },
-  }}
-/>
-```
-
-### Options
-
-```tsx
-headerSearchBarOptions: {
-  // Placeholder text
-  placeholder: "Search items...",
-
-  // Auto-capitalize behavior
-  autoCapitalize: "none",
-
-  // Input type
-  inputType: "text", // "text" | "phone" | "number" | "email"
-
-  // Cancel button text (iOS)
-  cancelButtonText: "Cancel",
-
-  // Hide when scrolling (iOS)
-  hideWhenScrolling: true,
-
-  // Hide navigation bar during search (iOS)
-  hideNavigationBar: true,
-
-  // Obscure background during search (iOS)
-  obscureBackground: true,
-
-  // Placement
-  placement: "automatic", // "automatic" | "inline" | "stacked"
-
-  // Callbacks
-  onChangeText: (event) => {},
-  onSearchButtonPress: (event) => {},
-  onCancelButtonPress: (event) => {},
-  onFocus: () => {},
-  onBlur: () => {},
-}
-```
-
-## useSearch Hook
-
-Reusable hook for search state management:
-
-```tsx
-import { useEffect, useState } from "react";
-import { useNavigation } from "expo-router";
-
-export function useSearch(options: any = {}) {
-  const [search, setSearch] = useState("");
-  const navigation = useNavigation();
-
-  useEffect(() => {
-    navigation.setOptions({
-      headerShown: true,
-      headerSearchBarOptions: {
-        ...options,
-        onChangeText(e: any) {
-          setSearch(e.nativeEvent.text);
-          options.onChangeText?.(e);
-        },
-        onSearchButtonPress(e: any) {
-          setSearch(e.nativeEvent.text);
-          options.onSearchButtonPress?.(e);
-        },
-        onCancelButtonPress(e: any) {
-          setSearch("");
-          options.onCancelButtonPress?.(e);
-        },
-      },
-    });
-  }, [options, navigation]);
-
-  return search;
-}
-```
-
-### Usage
-
-```tsx
-function SearchScreen() {
-  const search = useSearch({ placeholder: "Search items..." });
-
-  const filteredItems = items.filter(item =>
-    item.name.toLowerCase().includes(search.toLowerCase())
-  );
-
-  return (
-    <FlatList
-      data={filteredItems}
-      renderItem={({ item }) => <ItemRow item={item} />}
-    />
-  );
-}
-```
-
-## Filtering Patterns
-
-### Simple Text Filter
-
-```tsx
-const filtered = items.filter(item =>
-  item.name.toLowerCase().includes(search.toLowerCase())
-);
-```
-
-### Multiple Fields
-
-```tsx
-const filtered = items.filter(item => {
-  const query = search.toLowerCase();
-  return (
-    item.name.toLowerCase().includes(query) ||
-    item.description.toLowerCase().includes(query) ||
-    item.tags.some(tag => tag.toLowerCase().includes(query))
-  );
-});
-```
-
-### Debounced Search
-
-For expensive filtering or API calls:
-
-```tsx
-import { useState, useEffect, useMemo } from "react";
-
-function useDebounce<T>(value: T, delay: number): T {
-  const [debounced, setDebounced] = useState(value);
-
-  useEffect(() => {
-    const timer = setTimeout(() => setDebounced(value), delay);
-    return () => clearTimeout(timer);
-  }, [value, delay]);
-
-  return debounced;
-}
-
-function SearchScreen() {
-  const search = useSearch();
-  const debouncedSearch = useDebounce(search, 300);
-
-  const filteredItems = useMemo(() =>
-    items.filter(item =>
-      item.name.toLowerCase().includes(debouncedSearch.toLowerCase())
-    ),
-    [debouncedSearch]
-  );
-
-  return <FlatList data={filteredItems} />;
-}
-```
-
-## Search with Native Tabs
-
-When using NativeTabs with a search role, the search bar integrates with the tab bar:
-
-```tsx
-// app/_layout.tsx
-<NativeTabs>
-  <NativeTabs.Trigger name="(home)">
-    <Label>Home</Label>
-    <Icon sf="house.fill" />
-  </NativeTabs.Trigger>
-  <NativeTabs.Trigger name="(search)" role="search">
-    <Label>Search</Label>
-  </NativeTabs.Trigger>
-</NativeTabs>
-```
-
-```tsx
-// app/(search)/_layout.tsx
-<Stack>
-  <Stack.Screen
-    name="index"
-    options={{
-      headerSearchBarOptions: {
-        placeholder: "Search...",
-        onChangeText: (e) => setSearch(e.nativeEvent.text),
-      },
-    }}
-  />
-</Stack>
-```
-
-## Empty States
-
-Show appropriate UI when search returns no results:
-
-```tsx
-function SearchResults({ search, items }) {
-  const filtered = items.filter(/* ... */);
-
-  if (search && filtered.length === 0) {
-    return (
-      <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
-        <Text style={{ color: PlatformColor("secondaryLabel") }}>
-          No results for "{search}"
-        </Text>
-      </View>
-    );
-  }
-
-  return <FlatList data={filtered} />;
-}
-```
-
-## Search Suggestions
-
-Show recent searches or suggestions:
-
-```tsx
-function SearchScreen() {
-  const search = useSearch();
-  const [recentSearches, setRecentSearches] = useState<string[]>([]);
-
-  if (!search && recentSearches.length > 0) {
-    return (
-      <View>
-        <Text style={{ color: PlatformColor("secondaryLabel") }}>
-          Recent Searches
-        </Text>
-        {recentSearches.map((term) => (
-          <Pressable key={term} onPress={() => /* apply search */}>
-            <Text>{term}</Text>
-          </Pressable>
-        ))}
-      </View>
-    );
-  }
-
-  return <SearchResults search={search} />;
-}
-```

+ 0 - 121
neo/.agents/skills/building-native-ui/references/storage.md

@@ -1,121 +0,0 @@
-# Storage
-
-## Key-Value Storage
-
-Use the localStorage polyfill for key-value storage. **Never use AsyncStorage**
-
-```tsx
-import "expo-sqlite/localStorage/install";
-
-// Simple get/set
-localStorage.setItem("key", "value");
-localStorage.getItem("key");
-
-// Store objects as JSON
-localStorage.setItem("user", JSON.stringify({ name: "John", id: 1 }));
-const user = JSON.parse(localStorage.getItem("user") ?? "{}");
-```
-
-## When to Use What
-
-| Use Case                                             | Solution                |
-| ---------------------------------------------------- | ----------------------- |
-| Simple key-value (settings, preferences, small data) | `localStorage` polyfill |
-| Large datasets, complex queries, relational data     | Full `expo-sqlite`      |
-| Sensitive data (tokens, passwords)                   | `expo-secure-store`     |
-
-## Storage with React State
-
-Create a storage utility with subscriptions for reactive updates:
-
-```tsx
-// utils/storage.ts
-import "expo-sqlite/localStorage/install";
-
-type Listener = () => void;
-const listeners = new Map<string, Set<Listener>>();
-
-export const storage = {
-  get<T>(key: string, defaultValue: T): T {
-    const value = localStorage.getItem(key);
-    return value ? JSON.parse(value) : defaultValue;
-  },
-
-  set<T>(key: string, value: T): void {
-    localStorage.setItem(key, JSON.stringify(value));
-    listeners.get(key)?.forEach((fn) => fn());
-  },
-
-  subscribe(key: string, listener: Listener): () => void {
-    if (!listeners.has(key)) listeners.set(key, new Set());
-    listeners.get(key)!.add(listener);
-    return () => listeners.get(key)?.delete(listener);
-  },
-};
-```
-
-## React Hook for Storage
-
-```tsx
-// hooks/use-storage.ts
-import { useSyncExternalStore } from "react";
-import { storage } from "@/utils/storage";
-
-export function useStorage<T>(
-  key: string,
-  defaultValue: T
-): [T, (value: T) => void] {
-  const value = useSyncExternalStore(
-    (cb) => storage.subscribe(key, cb),
-    () => storage.get(key, defaultValue)
-  );
-
-  return [value, (newValue: T) => storage.set(key, newValue)];
-}
-```
-
-Usage:
-
-```tsx
-function Settings() {
-  const [theme, setTheme] = useStorage("theme", "light");
-
-  return (
-    <Switch
-      value={theme === "dark"}
-      onValueChange={(dark) => setTheme(dark ? "dark" : "light")}
-    />
-  );
-}
-```
-
-## Full SQLite for Complex Data
-
-For larger datasets or complex queries, use expo-sqlite directly:
-
-```tsx
-import * as SQLite from "expo-sqlite";
-
-const db = await SQLite.openDatabaseAsync("app.db");
-
-// Create table
-await db.execAsync(`
-  CREATE TABLE IF NOT EXISTS events (
-    id INTEGER PRIMARY KEY AUTOINCREMENT,
-    title TEXT NOT NULL,
-    date TEXT NOT NULL,
-    location TEXT
-  )
-`);
-
-// Insert
-await db.runAsync("INSERT INTO events (title, date) VALUES (?, ?)", [
-  "Meeting",
-  "2024-01-15",
-]);
-
-// Query
-const events = await db.getAllAsync("SELECT * FROM events WHERE date > ?", [
-  "2024-01-01",
-]);
-```

+ 0 - 433
neo/.agents/skills/building-native-ui/references/tabs.md

@@ -1,433 +0,0 @@
-# Native Tabs
-
-Always prefer NativeTabs from 'expo-router/unstable-native-tabs' for the best iOS experience.
-
-**SDK 54+. SDK 55 recommended.**
-
-## SDK Compatibility
-
-| Aspect        | SDK 54                                                  | SDK 55+                                                     |
-| ------------- | ------------------------------------------------------- | ----------------------------------------------------------- |
-| Import        | `import { NativeTabs, Icon, Label, Badge, VectorIcon }` | `import { NativeTabs }` only                                |
-| Icon          | `<Icon sf="house.fill" />`                              | `<NativeTabs.Trigger.Icon sf="house.fill" />`               |
-| Label         | `<Label>Home</Label>`                                   | `<NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>` |
-| Badge         | `<Badge>9+</Badge>`                                     | `<NativeTabs.Trigger.Badge>9+</NativeTabs.Trigger.Badge>`   |
-| Android icons | `drawable` prop                                         | `md` prop (Material Symbols)                                |
-
-All examples below use SDK 55 syntax. For SDK 54, replace `NativeTabs.Trigger.Icon/Label/Badge` with standalone `Icon`, `Label`, `Badge` imports.
-
-## Basic Usage
-
-```tsx
-import { NativeTabs } from "expo-router/unstable-native-tabs";
-
-export default function TabLayout() {
-  return (
-    <NativeTabs minimizeBehavior="onScrollDown">
-      <NativeTabs.Trigger name="index">
-        <NativeTabs.Trigger.Icon sf="house.fill" md="home" />
-        <NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
-        <NativeTabs.Trigger.Badge>9+</NativeTabs.Trigger.Badge>
-      </NativeTabs.Trigger>
-      <NativeTabs.Trigger name="settings">
-        <NativeTabs.Trigger.Icon sf="gear" md="settings" />
-        <NativeTabs.Trigger.Label>Settings</NativeTabs.Trigger.Label>
-      </NativeTabs.Trigger>
-      <NativeTabs.Trigger name="(search)" role="search">
-        <NativeTabs.Trigger.Label>Search</NativeTabs.Trigger.Label>
-      </NativeTabs.Trigger>
-    </NativeTabs>
-  );
-}
-```
-
-## Rules
-
-- You must include a trigger for each tab
-- The `NativeTabs.Trigger` 'name' must match the route name, including parentheses (e.g. `<NativeTabs.Trigger name="(search)">`)
-- Prefer search tab to be last in the list so it can combine with the search bar
-- Use the 'role' prop for common tab types
-- Tabs must be static — no dynamic addition/removal at runtime (remounts navigator, loses state)
-
-## Platform Features
-
-Native Tabs use platform-specific tab bar implementations:
-
-- **iOS 26+**: Liquid glass effects with system-native appearance
-- **Android**: Material 3 bottom navigation
-- Better performance and native feel
-
-## Icon Component
-
-```tsx
-// SF Symbol (iOS) + Material Symbol (Android)
-<NativeTabs.Trigger.Icon sf="house.fill" md="home" />
-
-// State variants
-<NativeTabs.Trigger.Icon sf={{ default: "house", selected: "house.fill" }} md="home" />
-
-// Custom image
-<NativeTabs.Trigger.Icon src={require('./icon.png')} />
-
-// Xcode asset catalog — iOS only (SDK 55+)
-<NativeTabs.Trigger.Icon xcasset="home-icon" />
-<NativeTabs.Trigger.Icon xcasset={{ default: "home-outline", selected: "home-filled" }} />
-
-// Rendering mode — iOS only (SDK 55+)
-<NativeTabs.Trigger.Icon src={require('./icon.png')} renderingMode="template" />
-<NativeTabs.Trigger.Icon src={require('./gradient.png')} renderingMode="original" />
-```
-
-`renderingMode`: `"template"` applies tint color (single-color icons), `"original"` preserves source colors (gradients). Android always uses original.
-
-## Label & Badge
-
-```tsx
-// Label
-<NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
-<NativeTabs.Trigger.Label hidden>Home</NativeTabs.Trigger.Label>  {/* icon-only tab */}
-
-// Badge
-<NativeTabs.Trigger.Badge>9+</NativeTabs.Trigger.Badge>
-<NativeTabs.Trigger.Badge />  {/* dot indicator */}
-```
-
-## iOS 26 Features
-
-### Liquid Glass Tab Bar
-
-The tab bar automatically adopts liquid glass appearance on iOS 26+.
-
-### Minimize on Scroll
-
-```tsx
-<NativeTabs minimizeBehavior="onScrollDown">
-```
-
-### Search Tab
-
-```tsx
-<NativeTabs.Trigger name="(search)" role="search">
-  <NativeTabs.Trigger.Label>Search</NativeTabs.Trigger.Label>
-</NativeTabs.Trigger>
-```
-
-**Note**: Place search tab last for best UX.
-
-### Role Prop
-
-Use semantic roles for special tab types:
-
-```tsx
-<NativeTabs.Trigger name="search" role="search" />
-<NativeTabs.Trigger name="favorites" role="favorites" />
-<NativeTabs.Trigger name="more" role="more" />
-```
-
-Available roles: `search` | `more` | `favorites` | `bookmarks` | `contacts` | `downloads` | `featured` | `history` | `mostRecent` | `mostViewed` | `recents` | `topRated`
-
-## Customization
-
-### Tint Color
-
-```tsx
-<NativeTabs tintColor="#007AFF">
-```
-
-### Dynamic Colors (iOS)
-
-Use DynamicColorIOS for colors that adapt to liquid glass:
-
-```tsx
-import { DynamicColorIOS, Platform } from 'react-native';
-
-const adaptiveBlue = Platform.select({
-  ios: DynamicColorIOS({ light: '#007AFF', dark: '#0A84FF' }),
-  default: '#007AFF',
-});
-
-<NativeTabs tintColor={adaptiveBlue}>
-```
-
-## Conditional Tabs
-
-```tsx
-<NativeTabs.Trigger name="admin" hidden={!isAdmin}>
-  <NativeTabs.Trigger.Label>Admin</NativeTabs.Trigger.Label>
-  <NativeTabs.Trigger.Icon sf="shield.fill" md="shield" />
-</NativeTabs.Trigger>
-```
-
-**Don't hide the tabs when they are visible - toggling visibility remounts the navigator; Do it only during the initial render.**
-
-**Note**: Hidden tabs cannot be navigated to!
-
-## Behavior Options
-
-```tsx
-<NativeTabs.Trigger
-  name="home"
-  disablePopToTop           // Don't pop stack when tapping active tab
-  disableScrollToTop        // Don't scroll to top when tapping active tab
-  disableAutomaticContentInsets  // Opt out of automatic safe area insets (SDK 55+)
->
-```
-
-## Hidden Tab Bar (SDK 55+)
-
-Use `hidden` prop on `NativeTabs` to hide the entire tab bar dynamically:
-
-```tsx
-<NativeTabs hidden={isTabBarHidden}>{/* triggers */}</NativeTabs>
-```
-
-## Bottom Accessory (SDK 55+)
-
-`NativeTabs.BottomAccessory` renders content above the tab bar (iOS 26+). Uses `usePlacement()` to adapt between `'regular'` and `'inline'` layouts.
-
-**Important**: Two instances render simultaneously — store state outside the component (props, context, or external store).
-
-```tsx
-import { NativeTabs } from "expo-router/unstable-native-tabs";
-import { useState } from "react";
-import { Pressable, Text, View } from "react-native";
-
-function MiniPlayer({
-  isPlaying,
-  onToggle,
-}: {
-  isPlaying: boolean;
-  onToggle: () => void;
-}) {
-  const placement = NativeTabs.BottomAccessory.usePlacement();
-  if (placement === "inline") {
-    return (
-      <Pressable onPress={onToggle}>
-        <SymbolView name={isPlaying ? "pause.fill" : "play.fill"} />
-      </Pressable>
-    );
-  }
-  return <View>{/* full player UI */}</View>;
-}
-
-export default function TabLayout() {
-  const [isPlaying, setIsPlaying] = useState(false);
-  return (
-    <NativeTabs>
-      <NativeTabs.BottomAccessory>
-        <MiniPlayer
-          isPlaying={isPlaying}
-          onToggle={() => setIsPlaying(!isPlaying)}
-        />
-      </NativeTabs.BottomAccessory>
-      <NativeTabs.Trigger name="index">
-        <NativeTabs.Trigger.Icon sf="house.fill" md="home" />
-        <NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
-      </NativeTabs.Trigger>
-    </NativeTabs>
-  );
-}
-```
-
-## Safe Area Handling (SDK 55+)
-
-SDK 55 handles safe areas automatically:
-
-- **Android**: Content wrapped in SafeAreaView (bottom inset)
-- **iOS**: First ScrollView gets automatic `contentInsetAdjustmentBehavior`
-
-To opt out per-tab, use `disableAutomaticContentInsets` and manage manually:
-
-```tsx
-<NativeTabs.Trigger name="index" disableAutomaticContentInsets>
-  <NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
-</NativeTabs.Trigger>
-```
-
-```tsx
-// In the screen
-import { SafeAreaView } from "react-native-screens/experimental";
-
-export default function HomeScreen() {
-  return (
-    <SafeAreaView edges={{ bottom: true }} style={{ flex: 1 }}>
-      {/* content */}
-    </SafeAreaView>
-  );
-}
-```
-
-## Using Vector Icons
-
-If you must use @expo/vector-icons instead of SF Symbols:
-
-```tsx
-import { NativeTabs } from "expo-router/unstable-native-tabs";
-import Ionicons from "@expo/vector-icons/Ionicons";
-
-<NativeTabs.Trigger name="home">
-  <NativeTabs.Trigger.VectorIcon vector={Ionicons} name="home" />
-  <NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
-</NativeTabs.Trigger>
-```
-
-**Prefer SF Symbols + `md` prop over vector icons for native feel.**
-
-If you are using SDK 55 and later **use the md prop to specify Material Symbols used on Android**.
-
-## Structure with Stacks
-
-Native tabs don't render headers. Nest Stacks inside each tab for navigation headers:
-
-```tsx
-// app/(tabs)/_layout.tsx
-import { NativeTabs } from "expo-router/unstable-native-tabs";
-
-export default function TabLayout() {
-  return (
-    <NativeTabs>
-      <NativeTabs.Trigger name="(home)">
-        <NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
-        <NativeTabs.Trigger.Icon sf="house.fill" md="home" />
-      </NativeTabs.Trigger>
-    </NativeTabs>
-  );
-}
-
-// app/(tabs)/(home)/_layout.tsx
-import Stack from "expo-router/stack";
-
-export default function HomeStack() {
-  return (
-    <Stack>
-      <Stack.Screen
-        name="index"
-        options={{ title: "Home", headerLargeTitle: true }}
-      />
-      <Stack.Screen name="details" options={{ title: "Details" }} />
-    </Stack>
-  );
-}
-```
-
-## Custom Web Layout
-
-Use platform-specific files for separate native and web tab layouts:
-
-```
-app/
-  _layout.tsx          # NativeTabs for iOS/Android
-  _layout.web.tsx      # Headless tabs for web (expo-router/ui)
-```
-
-Or extract to a component: `components/app-tabs.tsx` + `components/app-tabs.web.tsx`.
-
-## Migration from JS Tabs
-
-### Before (JS Tabs)
-
-```tsx
-import { Tabs } from "expo-router";
-
-<Tabs>
-  <Tabs.Screen
-    name="index"
-    options={{
-      title: "Home",
-      tabBarIcon: ({ color }) => <IconSymbol name="house.fill" color={color} />,
-      tabBarBadge: 3,
-    }}
-  />
-</Tabs>;
-```
-
-### After (Native Tabs)
-
-```tsx
-import { NativeTabs } from "expo-router/unstable-native-tabs";
-
-<NativeTabs>
-  <NativeTabs.Trigger name="index">
-    <NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
-    <NativeTabs.Trigger.Icon sf="house.fill" md="home" />
-    <NativeTabs.Trigger.Badge>3</NativeTabs.Trigger.Badge>
-  </NativeTabs.Trigger>
-</NativeTabs>;
-```
-
-### Key Differences
-
-| JS Tabs                    | Native Tabs                  |
-| -------------------------- | ---------------------------- |
-| `<Tabs.Screen>`            | `<NativeTabs.Trigger>`       |
-| `options={{ title }}`      | `<NativeTabs.Trigger.Label>` |
-| `options={{ tabBarIcon }}` | `<NativeTabs.Trigger.Icon>`  |
-| `tabBarBadge` option       | `<NativeTabs.Trigger.Badge>` |
-| Props-based API            | Component-based API          |
-| Headers built-in           | Nest `<Stack>` for headers   |
-
-## Limitations
-
-- **Android**: Maximum 5 tabs (Material Design constraint)
-- **Nesting**: Native tabs cannot nest inside other native tabs
-- **Tab bar height**: Cannot be measured programmatically
-- **FlatList transparency**: Use `disableTransparentOnScrollEdge` to fix issues
-- **Dynamic tabs**: Tabs must be static; changes remount navigator and lose state
-
-## Keyboard Handling (Android)
-
-Configure in app.json:
-
-```json
-{
-  "expo": {
-    "android": {
-      "softwareKeyboardLayoutMode": "resize"
-    }
-  }
-}
-```
-
-## Common Issues
-
-1. **Icons not showing on Android**: Add `md` prop (SDK 55) or use VectorIcon
-2. **Headers missing**: Nest a Stack inside each tab group
-3. **Trigger name mismatch**: `name` must match exact route name including parentheses
-4. **Badge not visible**: Badge must be a child of Trigger, not a prop
-5. **Tab bar transparent on iOS 18 and earlier**: If the screen uses a `ScrollView` or `FlatList`, make sure it is the first opaque child of the screen component. If it needs to be wrapped in another `View`, ensure the wrapper uses `collapsable={false}`. If the screen does not use a `ScrollView` or `FlatList`, set `disableTransparentOnScrollEdge` to `true` in the `NativeTabs.Trigger` options, to make the tab bar opaque.
-6. **Scroll to top not working**: Ensure `disableScrollToTop` is not set on the active tab's Trigger and `ScrollView` is the first child of the screen component.
-7. **Header buttons flicker when navigating between tabs**: Make sure the app is wrapped in a `ThemeProvider`
-
-```tsx
-import {
-  ThemeProvider,
-  DarkTheme,
-  DefaultTheme,
-} from "@react-navigation/native";
-import { useColorScheme } from "react-native";
-import { Stack } from "expo-router";
-
-export default function Layout() {
-  const colorScheme = useColorScheme();
-  return (
-    <ThemeProvider theme={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
-      <Stack />
-    </ThemeProvider>
-  );
-}
-```
-
-If the app only uses a light or dark theme, you can directly pass `DarkTheme` or `DefaultTheme` to `ThemeProvider` without checking the color scheme.
-
-```tsx
-import { ThemeProvider, DarkTheme } from "@react-navigation/native";
-import { Stack } from "expo-router";
-
-export default function Layout() {
-  return (
-    <ThemeProvider theme={DarkTheme}>
-      <Stack />
-    </ThemeProvider>
-  );
-}
-```

+ 0 - 284
neo/.agents/skills/building-native-ui/references/toolbar-and-headers.md

@@ -1,284 +0,0 @@
-# Toolbars and headers
-
-Add native iOS toolbar items to Stack screens. Items can be placed in the header (left/right) or in a bottom toolbar area.
-
-**Important:** iOS only. Available in Expo SDK 55+.
-
-## Notes app example
-
-```tsx
-import { Stack } from "expo-router";
-import { ScrollView } from "react-native";
-
-export default function FoldersScreen() {
-  return (
-    <>
-      {/* ScrollView must be the first child of the screen */}
-      <ScrollView
-        style={{ flex: 1 }}
-        contentInsetAdjustmentBehavior="automatic"
-      >
-        {/* Screen content */}
-      </ScrollView>
-      <Stack.Screen.Title large>Folders</Stack.Screen.Title>
-      <Stack.SearchBar placeholder="Search" onChangeText={() => {}} />
-      {/* Header toolbar - right side */}
-      <Stack.Toolbar placement="right">
-        <Stack.Toolbar.Button icon="folder.badge.plus" onPress={() => {}} />
-        <Stack.Toolbar.Button onPress={() => {}}>Edit</Stack.Toolbar.Button>
-      </Stack.Toolbar>
-
-      {/* Bottom toolbar */}
-      <Stack.Toolbar placement="bottom">
-        <Stack.Toolbar.SearchBarSlot />
-        <Stack.Toolbar.Button
-          icon="square.and.pencil"
-          onPress={() => {}}
-          separateBackground
-        />
-      </Stack.Toolbar>
-    </>
-  );
-}
-```
-
-## Mail inbox example
-
-```tsx
-import { Color, Stack } from "expo-router";
-import { useState } from "react";
-import { ScrollView, Text, View } from "react-native";
-
-export default function InboxScreen() {
-  const [isFilterOpen, setIsFilterOpen] = useState(false);
-  return (
-    <>
-      <ScrollView
-        style={{ flex: 1 }}
-        contentInsetAdjustmentBehavior="automatic"
-        contentContainerStyle={{ paddingHorizontal: 16 }}
-      >
-        {/* Screen content */}
-      </ScrollView>
-      <Stack.Screen options={{ headerTransparent: true }} />
-      <Stack.Screen.Title>Inbox</Stack.Screen.Title>
-      <Stack.SearchBar placeholder="Search" onChangeText={() => {}} />
-      {/* Header toolbar - right side */}
-      <Stack.Toolbar placement="right">
-        <Stack.Toolbar.Button onPress={() => {}}>Select</Stack.Toolbar.Button>
-        <Stack.Toolbar.Menu icon="ellipsis">
-          <Stack.Toolbar.Menu inline>
-            <Stack.Toolbar.Menu inline title="Sort By">
-              <Stack.Toolbar.MenuAction isOn>
-                Categories
-              </Stack.Toolbar.MenuAction>
-              <Stack.Toolbar.MenuAction>List</Stack.Toolbar.MenuAction>
-            </Stack.Toolbar.Menu>
-            <Stack.Toolbar.MenuAction icon="info.circle">
-              About categories
-            </Stack.Toolbar.MenuAction>
-          </Stack.Toolbar.Menu>
-          <Stack.Toolbar.MenuAction icon="person.circle">
-            Show Contact Photos
-          </Stack.Toolbar.MenuAction>
-        </Stack.Toolbar.Menu>
-      </Stack.Toolbar>
-
-      {/* Bottom toolbar */}
-      <Stack.Toolbar placement="bottom">
-        <Stack.Toolbar.Button
-          icon="line.3.horizontal.decrease"
-          selected={isFilterOpen}
-          onPress={() => setIsFilterOpen((prev) => !prev)}
-        />
-        <Stack.Toolbar.View hidden={!isFilterOpen}>
-          <View style={{ width: 70, height: 32, justifyContent: "center" }}>
-            <Text style={{ fontSize: 12, fontWeight: 700 }}>Filter by</Text>
-            <Text
-              style={{
-                fontSize: 12,
-                fontWeight: 700,
-                color: Color.ios.systemBlue,
-              }}
-            >
-              Unread
-            </Text>
-          </View>
-        </Stack.Toolbar.View>
-        <Stack.Toolbar.Spacer />
-        <Stack.Toolbar.SearchBarSlot />
-        <Stack.Toolbar.Button
-          icon="square.and.pencil"
-          onPress={() => {}}
-          separateBackground
-        />
-      </Stack.Toolbar>
-    </>
-  );
-}
-```
-
-## Placement
-
-- `"left"` - Header left
-- `"right"` - Header right
-- `"bottom"` (default) - Bottom toolbar
-
-## Components
-
-### Button
-
-- Icon button: `<Stack.Toolbar.Button icon="star.fill" onPress={() => {}} />`
-- Text button: `<Stack.Toolbar.Button onPress={() => {}}>Done</Stack.Toolbar.Button>`
-
-**Props:** `icon`, `image`, `onPress`, `disabled`, `hidden`, `variant` (`"plain"` | `"done"` | `"prominent"`), `tintColor`
-
-### Menu
-
-Dropdown menu for grouping actions.
-
-```tsx
-<Stack.Toolbar.Menu icon="ellipsis">
-  <Stack.Toolbar.Menu inline>
-    <Stack.Toolbar.MenuAction>Sort by Recently Added</Stack.Toolbar.MenuAction>
-    <Stack.Toolbar.MenuAction isOn>
-      Sort by Date Captured
-    </Stack.Toolbar.MenuAction>
-  </Stack.Toolbar.Menu>
-  <Stack.Toolbar.Menu title="Filter">
-    <Stack.Toolbar.Menu inline>
-      <Stack.Toolbar.MenuAction isOn icon="square.grid.2x2">
-        All Items
-      </Stack.Toolbar.MenuAction>
-    </Stack.Toolbar.Menu>
-    <Stack.Toolbar.MenuAction icon="heart">Favorites</Stack.Toolbar.MenuAction>
-    <Stack.Toolbar.MenuAction icon="photo">Photos</Stack.Toolbar.MenuAction>
-    <Stack.Toolbar.MenuAction icon="video">Videos</Stack.Toolbar.MenuAction>
-  </Stack.Toolbar.Menu>
-</Stack.Toolbar.Menu>
-```
-
-**Menu Props:** All Button props plus `title`, `inline`, `palette`, `elementSize` (`"small"` | `"medium"` | `"large"`)
-
-**MenuAction Props:** `icon`, `onPress`, `isOn`, `destructive`, `disabled`, `subtitle`
-
-When creating a palette with dividers, use `inline` combined with `elementSize="small"`. `palette` will not apply dividers on iOS 26.
-
-### Spacer
-
-```tsx
-<Stack.Toolbar.Spacer />           // Bottom toolbar - flexible
-<Stack.Toolbar.Spacer width={16} /> // Header - requires explicit width
-```
-
-### View
-
-Embed custom React Native components. When adding a custom view make sure that there is only a single child with **explicit width and height**.
-
-```tsx
-<Stack.Toolbar.View>
-  <View style={{ width: 70, height: 32, justifyContent: "center" }}>
-    <Text style={{ fontSize: 12, fontWeight: 700 }}>Filter by</Text>
-  </View>
-</Stack.Toolbar.View>
-```
-
-You can pass custom components to views as well:
-
-```tsx
-function CustomFilterView() {
-  return (
-    <View style={{ width: 70, height: 32, justifyContent: "center" }}>
-      <Text style={{ fontSize: 12, fontWeight: 700 }}>Filter by</Text>
-    </View>
-  );
-}
-...
-<Stack.Toolbar.View>
-  <CustomFilterView />
-</Stack.Toolbar.View>
-```
-
-## Recommendations
-
-- When creating more complex headers, extract them to a single component
-
-```tsx
-export default function Page() {
-  return (
-    <>
-      <ScrollView>{/* Screen content */}</ScrollView>
-      <InboxHeader />
-    </>
-  );
-}
-
-function InboxHeader() {
-  return (
-    <>
-      <Stack.Screen.Title>Inbox</Stack.Screen.Title>
-      <Stack.SearchBar placeholder="Search" onChangeText={() => {}} />
-      <Stack.Toolbar placement="right">{/* Toolbar buttons */}</Stack.Toolbar>
-    </>
-  );
-}
-```
-
-- When using `Stack.Toolbar`, make sure that all `Stack.Toolbar.*` components are wrapped inside `Stack.Toolbar` component.
-
-This will **not work**:
-
-```tsx
-function Buttons() {
-  return (
-    <>
-      <Stack.Toolbar.Button icon="star.fill" onPress={() => {}} />
-      <Stack.Toolbar.Button onPress={() => {}}>Done</Stack.Toolbar.Button>
-    </>
-  );
-}
-
-function Page() {
-  return (
-    <>
-      <ScrollView>{/* Screen content */}</ScrollView>
-      <Stack.Toolbar placement="right">
-        <Buttons /> {/* ❌ This will NOT work */}
-      </Stack.Toolbar>
-    </>
-  );
-}
-```
-
-This will work:
-
-```tsx
-function ToolbarWithButtons() {
-  return (
-    <Stack.Toolbar>
-      <Stack.Toolbar.Button icon="star.fill" onPress={() => {}} />
-      <Stack.Toolbar.Button onPress={() => {}}>Done</Stack.Toolbar.Button>
-    </Stack.Toolbar>
-  );
-}
-
-function Page() {
-  return (
-    <>
-      <ScrollView>{/* Screen content */}</ScrollView>
-      <ToolbarWithButtons /> {/* ✅ This will work */}
-    </>
-  );
-}
-```
-
-## Limitations
-
-- iOS only
-- `placement="bottom"` can only be used inside screen components (not in layout files)
-- `Stack.Toolbar.Badge` only works with `placement="left"` or `"right"`
-- Header Spacers require explicit `width`
-
-## Reference
-
-Docs https://docs.expo.dev/versions/unversioned/sdk/router - read to see the full API.

+ 0 - 197
neo/.agents/skills/building-native-ui/references/visual-effects.md

@@ -1,197 +0,0 @@
-# Visual Effects
-
-## Backdrop Blur
-
-Use `expo-blur` for blur effects. Prefer systemMaterial tints as they adapt to dark mode.
-
-```tsx
-import { BlurView } from "expo-blur";
-
-<BlurView tint="systemMaterial" intensity={100} />;
-```
-
-### Tint Options
-
-```tsx
-// System materials (adapt to dark mode)
-<BlurView tint="systemMaterial" />
-<BlurView tint="systemThinMaterial" />
-<BlurView tint="systemUltraThinMaterial" />
-<BlurView tint="systemThickMaterial" />
-<BlurView tint="systemChromeMaterial" />
-
-// Basic tints
-<BlurView tint="light" />
-<BlurView tint="dark" />
-<BlurView tint="default" />
-
-// Prominent (more visible)
-<BlurView tint="prominent" />
-
-// Extra light/dark
-<BlurView tint="extraLight" />
-```
-
-### Intensity
-
-Control blur strength with `intensity` (0-100):
-
-```tsx
-<BlurView tint="systemMaterial" intensity={50} />  // Subtle
-<BlurView tint="systemMaterial" intensity={100} /> // Full
-```
-
-### Rounded Corners
-
-BlurView requires `overflow: 'hidden'` to clip rounded corners:
-
-```tsx
-<BlurView
-  tint="systemMaterial"
-  intensity={100}
-  style={{
-    borderRadius: 16,
-    overflow: 'hidden',
-  }}
-/>
-```
-
-### Overlay Pattern
-
-Common pattern for overlaying blur on content:
-
-```tsx
-<View style={{ position: 'relative' }}>
-  <Image source={{ uri: '...' }} style={{ width: '100%', height: 200 }} />
-  <BlurView
-    tint="systemUltraThinMaterial"
-    intensity={80}
-    style={{
-      position: 'absolute',
-      bottom: 0,
-      left: 0,
-      right: 0,
-      padding: 16,
-    }}
-  >
-    <Text style={{ color: 'white' }}>Caption</Text>
-  </BlurView>
-</View>
-```
-
-## Glass Effects (iOS 26+)
-
-Use `expo-glass-effect` for liquid glass backdrops on iOS 26+.
-
-```tsx
-import { GlassView } from "expo-glass-effect";
-
-<GlassView style={{ borderRadius: 16, padding: 16 }}>
-  <Text>Content inside glass</Text>
-</GlassView>
-```
-
-### Interactive Glass
-
-Add `isInteractive` for buttons and pressable glass:
-
-```tsx
-import { GlassView } from "expo-glass-effect";
-import { SymbolView } from "expo-symbols";
-import { PlatformColor } from "react-native";
-
-<GlassView isInteractive style={{ borderRadius: 50 }}>
-  <Pressable style={{ padding: 12 }} onPress={handlePress}>
-    <SymbolView name="plus" tintColor={PlatformColor("label")} size={36} />
-  </Pressable>
-</GlassView>
-```
-
-### Glass Buttons
-
-Create liquid glass buttons:
-
-```tsx
-function GlassButton({ icon, onPress }) {
-  return (
-    <GlassView isInteractive style={{ borderRadius: 50 }}>
-      <Pressable style={{ padding: 12 }} onPress={onPress}>
-        <SymbolView name={icon} tintColor={PlatformColor("label")} size={24} />
-      </Pressable>
-    </GlassView>
-  );
-}
-
-// Usage
-<GlassButton icon="plus" onPress={handleAdd} />
-<GlassButton icon="gear" onPress={handleSettings} />
-```
-
-### Glass Card
-
-```tsx
-<GlassView style={{ borderRadius: 20, padding: 20 }}>
-  <Text style={{ fontSize: 18, fontWeight: '600', color: PlatformColor("label") }}>
-    Card Title
-  </Text>
-  <Text style={{ color: PlatformColor("secondaryLabel"), marginTop: 8 }}>
-    Card content goes here
-  </Text>
-</GlassView>
-```
-
-### Checking Availability
-
-```tsx
-import { isLiquidGlassAvailable } from "expo-glass-effect";
-
-if (isLiquidGlassAvailable()) {
-  // Use GlassView
-} else {
-  // Fallback to BlurView or solid background
-}
-```
-
-### Fallback Pattern
-
-```tsx
-import { GlassView, isLiquidGlassAvailable } from "expo-glass-effect";
-import { BlurView } from "expo-blur";
-
-function AdaptiveGlass({ children, style }) {
-  if (isLiquidGlassAvailable()) {
-    return <GlassView style={style}>{children}</GlassView>;
-  }
-
-  return (
-    <BlurView tint="systemMaterial" intensity={80} style={style}>
-      {children}
-    </BlurView>
-  );
-}
-```
-
-## Sheet with Glass Background
-
-Make sheet backgrounds liquid glass on iOS 26+:
-
-```tsx
-<Stack.Screen
-  name="sheet"
-  options={{
-    presentation: "formSheet",
-    sheetGrabberVisible: true,
-    sheetAllowedDetents: [0.5, 1.0],
-    contentStyle: { backgroundColor: "transparent" },
-  }}
-/>
-```
-
-## Best Practices
-
-- Use `systemMaterial` tints for automatic dark mode support
-- Always set `overflow: 'hidden'` on BlurView for rounded corners
-- Use `isInteractive` on GlassView for buttons and pressables
-- Check `isLiquidGlassAvailable()` and provide fallbacks
-- Avoid nesting blur views (performance impact)
-- Keep blur intensity reasonable (50-100) for readability

+ 0 - 605
neo/.agents/skills/building-native-ui/references/webgpu-three.md

@@ -1,605 +0,0 @@
-# WebGPU & Three.js for Expo
-
-**Use this skill for ANY 3D graphics, games, GPU compute, or Three.js features in React Native.**
-
-## Locked Versions (Tested & Working)
-
-```json
-{
-  "react-native-wgpu": "^0.4.1",
-  "three": "0.172.0",
-  "@react-three/fiber": "^9.4.0",
-  "wgpu-matrix": "^3.0.2",
-  "@types/three": "0.172.0"
-}
-```
-
-**Critical:** These versions are tested together. Mismatched versions cause type errors and runtime issues.
-
-## Installation
-
-```bash
-npm install react-native-wgpu@^0.4.1 three@0.172.0 @react-three/fiber@^9.4.0 wgpu-matrix@^3.0.2 @types/three@0.172.0 --legacy-peer-deps
-```
-
-**Note:** `--legacy-peer-deps` may be required due to peer dependency conflicts with canary Expo versions.
-
-## Metro Configuration
-
-Create `metro.config.js` in project root:
-
-```js
-const { getDefaultConfig } = require("expo/metro-config");
-
-const config = getDefaultConfig(__dirname);
-
-config.resolver.resolveRequest = (context, moduleName, platform) => {
-  // Force 'three' to webgpu build
-  if (moduleName.startsWith("three")) {
-    moduleName = "three/webgpu";
-  }
-
-  // Use standard react-three/fiber instead of React Native version
-  if (platform !== "web" && moduleName.startsWith("@react-three/fiber")) {
-    return context.resolveRequest(
-      {
-        ...context,
-        unstable_conditionNames: ["module"],
-        mainFields: ["module"],
-      },
-      moduleName,
-      platform
-    );
-  }
-  return context.resolveRequest(context, moduleName, platform);
-};
-
-module.exports = config;
-```
-
-## Required Lib Files
-
-Create these files in `src/lib/`:
-
-### 1. make-webgpu-renderer.ts
-
-```ts
-import type { NativeCanvas } from "react-native-wgpu";
-import * as THREE from "three/webgpu";
-
-export class ReactNativeCanvas {
-  constructor(private canvas: NativeCanvas) {}
-
-  get width() {
-    return this.canvas.width;
-  }
-  get height() {
-    return this.canvas.height;
-  }
-  set width(width: number) {
-    this.canvas.width = width;
-  }
-  set height(height: number) {
-    this.canvas.height = height;
-  }
-  get clientWidth() {
-    return this.canvas.width;
-  }
-  get clientHeight() {
-    return this.canvas.height;
-  }
-  set clientWidth(width: number) {
-    this.canvas.width = width;
-  }
-  set clientHeight(height: number) {
-    this.canvas.height = height;
-  }
-
-  addEventListener(_type: string, _listener: EventListener) {}
-  removeEventListener(_type: string, _listener: EventListener) {}
-  dispatchEvent(_event: Event) {}
-  setPointerCapture() {}
-  releasePointerCapture() {}
-}
-
-export const makeWebGPURenderer = (
-  context: GPUCanvasContext,
-  { antialias = true }: { antialias?: boolean } = {}
-) =>
-  new THREE.WebGPURenderer({
-    antialias,
-    // @ts-expect-error
-    canvas: new ReactNativeCanvas(context.canvas),
-    context,
-  });
-```
-
-### 2. fiber-canvas.tsx
-
-```tsx
-import * as THREE from "three/webgpu";
-import React, { useEffect, useRef } from "react";
-import type { ReconcilerRoot, RootState } from "@react-three/fiber";
-import {
-  extend,
-  createRoot,
-  unmountComponentAtNode,
-  events,
-} from "@react-three/fiber";
-import type { ViewProps } from "react-native";
-import { PixelRatio } from "react-native";
-import { Canvas, type CanvasRef } from "react-native-wgpu";
-
-import {
-  makeWebGPURenderer,
-  ReactNativeCanvas,
-} from "@/lib/make-webgpu-renderer";
-
-// Extend THREE namespace for R3F - add all components you use
-extend({
-  AmbientLight: THREE.AmbientLight,
-  DirectionalLight: THREE.DirectionalLight,
-  PointLight: THREE.PointLight,
-  SpotLight: THREE.SpotLight,
-  Mesh: THREE.Mesh,
-  Group: THREE.Group,
-  Points: THREE.Points,
-  BoxGeometry: THREE.BoxGeometry,
-  SphereGeometry: THREE.SphereGeometry,
-  CylinderGeometry: THREE.CylinderGeometry,
-  ConeGeometry: THREE.ConeGeometry,
-  DodecahedronGeometry: THREE.DodecahedronGeometry,
-  BufferGeometry: THREE.BufferGeometry,
-  BufferAttribute: THREE.BufferAttribute,
-  MeshStandardMaterial: THREE.MeshStandardMaterial,
-  MeshBasicMaterial: THREE.MeshBasicMaterial,
-  PointsMaterial: THREE.PointsMaterial,
-  PerspectiveCamera: THREE.PerspectiveCamera,
-  Scene: THREE.Scene,
-});
-
-interface FiberCanvasProps {
-  children: React.ReactNode;
-  style?: ViewProps["style"];
-  camera?: THREE.PerspectiveCamera;
-  scene?: THREE.Scene;
-}
-
-export const FiberCanvas = ({
-  children,
-  style,
-  scene,
-  camera,
-}: FiberCanvasProps) => {
-  const root = useRef<ReconcilerRoot<OffscreenCanvas>>(null!);
-  const canvasRef = useRef<CanvasRef>(null);
-
-  useEffect(() => {
-    const context = canvasRef.current!.getContext("webgpu")!;
-    const renderer = makeWebGPURenderer(context);
-
-    // @ts-expect-error - ReactNativeCanvas wraps native canvas
-    const canvas = new ReactNativeCanvas(context.canvas) as HTMLCanvasElement;
-    canvas.width = canvas.clientWidth * PixelRatio.get();
-    canvas.height = canvas.clientHeight * PixelRatio.get();
-    const size = {
-      top: 0,
-      left: 0,
-      width: canvas.clientWidth,
-      height: canvas.clientHeight,
-    };
-
-    if (!root.current) {
-      root.current = createRoot(canvas);
-    }
-    root.current.configure({
-      size,
-      events,
-      scene,
-      camera,
-      gl: renderer,
-      frameloop: "always",
-      dpr: 1,
-      onCreated: async (state: RootState) => {
-        // @ts-expect-error - WebGPU renderer has init method
-        await state.gl.init();
-        const renderFrame = state.gl.render.bind(state.gl);
-        state.gl.render = (s: THREE.Scene, c: THREE.Camera) => {
-          renderFrame(s, c);
-          context?.present();
-        };
-      },
-    });
-    root.current.render(children);
-    return () => {
-      if (canvas != null) {
-        unmountComponentAtNode(canvas!);
-      }
-    };
-  });
-
-  return <Canvas ref={canvasRef} style={style} />;
-};
-```
-
-## Basic 3D Scene
-
-```tsx
-import * as THREE from "three/webgpu";
-import { View } from "react-native";
-import { useRef } from "react";
-import { useFrame, useThree } from "@react-three/fiber";
-import { FiberCanvas } from "@/lib/fiber-canvas";
-
-function RotatingBox() {
-  const ref = useRef<THREE.Mesh>(null!);
-
-  useFrame((_, delta) => {
-    ref.current.rotation.x += delta;
-    ref.current.rotation.y += delta * 0.5;
-  });
-
-  return (
-    <mesh ref={ref}>
-      <boxGeometry args={[1, 1, 1]} />
-      <meshStandardMaterial color="hotpink" />
-    </mesh>
-  );
-}
-
-function Scene() {
-  const { camera } = useThree();
-
-  useEffect(() => {
-    camera.position.set(0, 2, 5);
-    camera.lookAt(0, 0, 0);
-  }, [camera]);
-
-  return (
-    <>
-      <ambientLight intensity={0.5} />
-      <directionalLight position={[10, 10, 5]} intensity={1} />
-      <RotatingBox />
-    </>
-  );
-}
-
-export default function App() {
-  return (
-    <View style={{ flex: 1 }}>
-      <FiberCanvas style={{ flex: 1 }}>
-        <Scene />
-      </FiberCanvas>
-    </View>
-  );
-}
-```
-
-## Lazy Loading (Recommended)
-
-Use React.lazy to code-split Three.js for better loading:
-
-```tsx
-import React, { Suspense } from "react";
-import { ActivityIndicator, View } from "react-native";
-
-const Scene = React.lazy(() => import("@/components/scene"));
-
-export default function Page() {
-  return (
-    <View style={{ flex: 1 }}>
-      <Suspense fallback={<ActivityIndicator size="large" />}>
-        <Scene />
-      </Suspense>
-    </View>
-  );
-}
-```
-
-## Common Geometries
-
-```tsx
-// Box
-<mesh>
-  <boxGeometry args={[width, height, depth]} />
-  <meshStandardMaterial color="red" />
-</mesh>
-
-// Sphere
-<mesh>
-  <sphereGeometry args={[radius, widthSegments, heightSegments]} />
-  <meshStandardMaterial color="blue" />
-</mesh>
-
-// Cylinder
-<mesh>
-  <cylinderGeometry args={[radiusTop, radiusBottom, height, segments]} />
-  <meshStandardMaterial color="green" />
-</mesh>
-
-// Cone
-<mesh>
-  <coneGeometry args={[radius, height, segments]} />
-  <meshStandardMaterial color="yellow" />
-</mesh>
-```
-
-## Lighting
-
-```tsx
-// Ambient (uniform light everywhere)
-<ambientLight intensity={0.5} />
-
-// Directional (sun-like)
-<directionalLight position={[10, 10, 5]} intensity={1} />
-
-// Point (light bulb)
-<pointLight position={[0, 5, 0]} intensity={2} distance={10} />
-
-// Spot (flashlight)
-<spotLight position={[0, 10, 0]} angle={0.3} penumbra={1} intensity={2} />
-```
-
-## Animation with useFrame
-
-```tsx
-import { useFrame } from "@react-three/fiber";
-import { useRef } from "react";
-import * as THREE from "three/webgpu";
-
-function AnimatedMesh() {
-  const ref = useRef<THREE.Mesh>(null!);
-
-  // Runs every frame - delta is time since last frame
-  useFrame((state, delta) => {
-    // Rotate
-    ref.current.rotation.y += delta;
-
-    // Oscillate position
-    ref.current.position.y = Math.sin(state.clock.elapsedTime) * 2;
-  });
-
-  return (
-    <mesh ref={ref}>
-      <boxGeometry />
-      <meshStandardMaterial color="orange" />
-    </mesh>
-  );
-}
-```
-
-## Particle Systems
-
-```tsx
-import * as THREE from "three/webgpu";
-import { useRef, useEffect } from "react";
-import { useFrame } from "@react-three/fiber";
-
-function Particles({ count = 500 }) {
-  const ref = useRef<THREE.Points>(null!);
-  const positions = useRef<Float32Array>(new Float32Array(count * 3));
-
-  useEffect(() => {
-    for (let i = 0; i < count; i++) {
-      positions.current[i * 3] = (Math.random() - 0.5) * 50;
-      positions.current[i * 3 + 1] = (Math.random() - 0.5) * 50;
-      positions.current[i * 3 + 2] = (Math.random() - 0.5) * 50;
-    }
-  }, [count]);
-
-  useFrame((_, delta) => {
-    // Animate particles
-    for (let i = 0; i < count; i++) {
-      positions.current[i * 3 + 1] -= delta * 2;
-      if (positions.current[i * 3 + 1] < -25) {
-        positions.current[i * 3 + 1] = 25;
-      }
-    }
-    ref.current.geometry.attributes.position.needsUpdate = true;
-  });
-
-  return (
-    <points ref={ref}>
-      <bufferGeometry>
-        <bufferAttribute
-          attach="attributes-position"
-          args={[positions.current, 3]}
-        />
-      </bufferGeometry>
-      <pointsMaterial color="#ffffff" size={0.2} sizeAttenuation />
-    </points>
-  );
-}
-```
-
-## Touch Controls (Orbit)
-
-See the full `orbit-controls.tsx` implementation in the lib files. Usage:
-
-```tsx
-import { View } from "react-native";
-import { FiberCanvas } from "@/lib/fiber-canvas";
-import useControls from "@/lib/orbit-controls";
-
-function Scene() {
-  const [OrbitControls, events] = useControls();
-
-  return (
-    <View style={{ flex: 1 }} {...events}>
-      <FiberCanvas style={{ flex: 1 }}>
-        <OrbitControls />
-        {/* Your 3D content */}
-      </FiberCanvas>
-    </View>
-  );
-}
-```
-
-## Common Issues & Solutions
-
-### 1. "X is not part of the THREE namespace"
-
-**Problem:** Error like `AmbientLight is not part of the THREE namespace`
-
-**Solution:** Add the missing component to the `extend()` call in fiber-canvas.tsx:
-
-```tsx
-extend({
-  AmbientLight: THREE.AmbientLight,
-  // Add other missing components...
-});
-```
-
-### 2. TypeScript Errors with Three.js
-
-**Problem:** Type mismatches between three.js and R3F
-
-**Solution:** Use `@ts-expect-error` comments where needed:
-
-```tsx
-// @ts-expect-error - WebGPU renderer types don't match
-await state.gl.init();
-```
-
-### 3. Blank Screen
-
-**Problem:** Canvas renders but nothing visible
-
-**Solution:**
-
-1. Ensure camera is positioned correctly and looking at scene
-2. Add lighting (objects are black without light)
-3. Check that `extend()` includes all components used
-
-### 4. Performance Issues
-
-**Problem:** Low frame rate or stuttering
-
-**Solution:**
-
-- Reduce polygon count in geometries
-- Use `useMemo` for static data
-- Limit particle count
-- Use `instancedMesh` for many identical objects
-
-### 5. Peer Dependency Errors
-
-**Problem:** npm install fails with ERESOLVE
-
-**Solution:** Use `--legacy-peer-deps`:
-
-```bash
-npm install <packages> --legacy-peer-deps
-```
-
-## Building
-
-WebGPU requires a custom build:
-
-```bash
-npx expo prebuild
-npx expo run:ios
-```
-
-**Note:** WebGPU does NOT work in Expo Go.
-
-## File Structure
-
-```
-src/
-├── app/
-│   └── index.tsx           # Entry point with lazy loading
-├── components/
-│   ├── scene.tsx           # Main 3D scene
-│   └── game.tsx            # Game logic
-└── lib/
-    ├── fiber-canvas.tsx    # R3F canvas wrapper
-    ├── make-webgpu-renderer.ts  # WebGPU renderer
-    └── orbit-controls.tsx  # Touch controls
-```
-
-## Decision Tree
-
-```
-Need 3D graphics?
-├── Simple shapes → mesh + geometry + material
-├── Animated objects → useFrame + refs
-├── Many objects → instancedMesh
-├── Particles → Points + BufferGeometry
-│
-Need interaction?
-├── Orbit camera → useControls hook
-├── Touch objects → onClick on mesh
-├── Gestures → react-native-gesture-handler
-│
-Performance critical?
-├── Static geometry → useMemo
-├── Many instances → InstancedMesh
-└── Complex scenes → LOD (Level of Detail)
-```
-
-## Example: Complete Game Scene
-
-```tsx
-import * as THREE from "three/webgpu";
-import { View, Text, Pressable } from "react-native";
-import { useRef, useState, useCallback } from "react";
-import { useFrame, useThree } from "@react-three/fiber";
-import { FiberCanvas } from "@/lib/fiber-canvas";
-
-function Player({ position }: { position: THREE.Vector3 }) {
-  const ref = useRef<THREE.Mesh>(null!);
-
-  useFrame(() => {
-    ref.current.position.copy(position);
-  });
-
-  return (
-    <mesh ref={ref}>
-      <coneGeometry args={[0.5, 1, 8]} />
-      <meshStandardMaterial color="#00ffff" />
-    </mesh>
-  );
-}
-
-function GameScene({ playerX }: { playerX: number }) {
-  const { camera } = useThree();
-  const playerPos = useRef(new THREE.Vector3(0, 0, 0));
-
-  playerPos.current.x = playerX;
-
-  useEffect(() => {
-    camera.position.set(0, 10, 15);
-    camera.lookAt(0, 0, 0);
-  }, [camera]);
-
-  return (
-    <>
-      <ambientLight intensity={0.5} />
-      <directionalLight position={[5, 10, 5]} />
-      <Player position={playerPos.current} />
-    </>
-  );
-}
-
-export default function Game() {
-  const [playerX, setPlayerX] = useState(0);
-
-  return (
-    <View style={{ flex: 1, backgroundColor: "#000" }}>
-      <FiberCanvas style={{ flex: 1 }}>
-        <GameScene playerX={playerX} />
-      </FiberCanvas>
-
-      <View style={{ position: "absolute", bottom: 40, flexDirection: "row" }}>
-        <Pressable onPress={() => setPlayerX((x) => x - 1)}>
-          <Text style={{ color: "#fff", fontSize: 32 }}>◀</Text>
-        </Pressable>
-        <Pressable onPress={() => setPlayerX((x) => x + 1)}>
-          <Text style={{ color: "#fff", fontSize: 32 }}>▶</Text>
-        </Pressable>
-      </View>
-    </View>
-  );
-}
-```

+ 0 - 158
neo/.agents/skills/building-native-ui/references/zoom-transitions.md

@@ -1,158 +0,0 @@
-# Apple Zoom Transitions
-
-Fluid zoom transitions for navigating between screens. iOS 18+, Expo SDK 55+, Stack navigator only.
-
-```tsx
-import { Link } from "expo-router";
-```
-
-## Basic Zoom
-
-Use `withAppleZoom` on `Link.Trigger` to zoom the entire trigger element into the destination screen:
-
-```tsx
-<Link href="/photo" asChild>
-  <Link.Trigger withAppleZoom>
-    <Pressable>
-      <Image
-        source={{ uri: "https://example.com/thumb.jpg" }}
-        style={{ width: 120, height: 120, borderRadius: 12 }}
-      />
-    </Pressable>
-  </Link.Trigger>
-</Link>
-```
-
-## Targeted Zoom with `Link.AppleZoom`
-
-Wrap only the element that should animate. Siblings outside `Link.AppleZoom` are not part of the transition:
-
-```tsx
-<Link href="/photo" asChild>
-  <Link.Trigger>
-    <Pressable style={{ alignItems: "center" }}>
-      <Link.AppleZoom>
-        <Image
-          source={{ uri: "https://example.com/thumb.jpg" }}
-          style={{ width: 200, aspectRatio: 4 / 3 }}
-        />
-      </Link.AppleZoom>
-      <Text>Caption text (not zoomed)</Text>
-    </Pressable>
-  </Link.Trigger>
-</Link>
-```
-
-`Link.AppleZoom` accepts only a single child element.
-
-## Destination Target
-
-Use `Link.AppleZoomTarget` on the destination screen to align the zoom animation to a specific element:
-
-```tsx
-// Destination screen (e.g., app/photo.tsx)
-import { Link } from "expo-router";
-
-export default function PhotoScreen() {
-  return (
-    <View style={{ flex: 1 }}>
-      <Link.AppleZoomTarget>
-        <Image
-          source={{ uri: "https://example.com/full.jpg" }}
-          style={{ width: "100%", aspectRatio: 4 / 3 }}
-        />
-      </Link.AppleZoomTarget>
-      <Text>Photo details below</Text>
-    </View>
-  );
-}
-```
-
-Without a target, the zoom animates to fill the entire destination screen.
-
-## Custom Alignment Rectangle
-
-For manual control over where the zoom lands on the destination, use `alignmentRect` instead of `Link.AppleZoomTarget`:
-
-```tsx
-<Link.AppleZoom alignmentRect={{ x: 0, y: 0, width: 200, height: 300 }}>
-  <Image source={{ uri: "https://example.com/thumb.jpg" }} />
-</Link.AppleZoom>
-```
-
-Coordinates are in the destination screen's coordinate space. Prefer `Link.AppleZoomTarget` when possible — use `alignmentRect` only when the target element isn't available as a React component.
-
-## Controlling Dismissal
-
-Zoom screens support interactive dismissal gestures by default (pinch, swipe down when scrolled to top, swipe from leading edge). Use `usePreventZoomTransitionDismissal` on the destination screen to control this.
-
-### Disable all dismissal gestures
-
-```tsx
-import { usePreventZoomTransitionDismissal } from "expo-router";
-
-export default function PhotoScreen() {
-  usePreventZoomTransitionDismissal();
-  return <Image source={{ uri: "https://example.com/full.jpg" }} />;
-}
-```
-
-### Restrict dismissal to a specific area
-
-Use `unstable_dismissalBoundsRect` to prevent conflicts with scrollable content:
-
-```tsx
-usePreventZoomTransitionDismissal({
-  unstable_dismissalBoundsRect: {
-    minX: 0,
-    minY: 0,
-    maxX: 300,
-    maxY: 300,
-  },
-});
-```
-
-This is useful when the destination contains a zoomable scroll view — the system gives that scroll view precedence over the dismiss gesture.
-
-## Combining with Link.Preview
-
-Zoom transitions work alongside long-press previews:
-
-```tsx
-<Link href="/photo" asChild>
-  <Link.Trigger withAppleZoom>
-    <Pressable>
-      <Image
-        source={{ uri: "https://example.com/thumb.jpg" }}
-        style={{ width: 120, height: 120 }}
-      />
-    </Pressable>
-  </Link.Trigger>
-  <Link.Preview />
-</Link>
-```
-
-## Best Practices
-
-**Good use cases:**
-- Thumbnail → full image (gallery, profile photos)
-- Card → detail screen with similar visual content
-- Source and destination with similar aspect ratios
-
-**Avoid:**
-- Skinny full-width list rows as zoom sources — the transition looks unnatural
-- Mismatched aspect ratios between source and destination without `alignmentRect`
-- Using zoom with sheets or popovers — only works in Stack navigator
-- Hiding the navigation bar — known issues with header visibility during transitions
-
-**Tips:**
-- Always provide a close or back button — dismissal gestures are not discoverable
-- If the destination has a zoomable scroll view, use `unstable_dismissalBoundsRect` to avoid gesture conflicts
-- Source view doesn't need to match the tap target — only the `Link.AppleZoom` wrapped element animates
-- When source is unavailable (e.g., scrolled off screen), the transition zooms from the center of the screen
-
-## References
-
-- Expo Router Zoom Transitions: https://docs.expo.dev/router/advanced/zoom-transition/
-- Link.AppleZoom API: https://docs.expo.dev/versions/v55.0.0/sdk/router/#linkapplezoom
-- Apple UIKit Fluid Transitions: https://developer.apple.com/documentation/uikit/enhancing-your-app-with-fluid-transitions

+ 0 - 296
neo/.agents/skills/deploy-to-vercel/SKILL.md

@@ -1,296 +0,0 @@
----
-name: deploy-to-vercel
-description: Deploy applications and websites to Vercel. Use when the user requests deployment actions like "deploy my app", "deploy and give me the link", "push this live", or "create a preview deployment".
-metadata:
-  author: vercel
-  version: "3.0.0"
----
-
-# Deploy to Vercel
-
-Deploy any project to Vercel. **Always deploy as preview** (not production) unless the user explicitly asks for production.
-
-The goal is to get the user into the best long-term setup: their project linked to Vercel with git-push deploys. Every method below tries to move the user closer to that state.
-
-## Step 1: Gather Project State
-
-Run all four checks before deciding which method to use:
-
-```bash
-# 1. Check for a git remote
-git remote get-url origin 2>/dev/null
-
-# 2. Check if locally linked to a Vercel project (either file means linked)
-cat .vercel/project.json 2>/dev/null || cat .vercel/repo.json 2>/dev/null
-
-# 3. Check if the Vercel CLI is installed and authenticated
-vercel whoami 2>/dev/null
-
-# 4. List available teams (if authenticated)
-vercel teams list --format json 2>/dev/null
-```
-
-### Team selection
-
-If the user belongs to multiple teams, present all available team slugs as a bulleted list and ask which one to deploy to. Once the user picks a team, proceed immediately to the next step — do not ask for additional confirmation.
-
-Pass the team slug via `--scope` on all subsequent CLI commands (`vercel deploy`, `vercel link`, `vercel inspect`, etc.):
-
-```bash
-vercel deploy [path] -y --no-wait --scope <team-slug>
-```
-
-If the project is already linked (`.vercel/project.json` or `.vercel/repo.json` exists), the `orgId` in those files determines the team — no need to ask again. If there is only one team (or just a personal account), skip the prompt and use it directly.
-
-**About the `.vercel/` directory:** A linked project has either:
-- `.vercel/project.json` — created by `vercel link` (single project linking). Contains `projectId` and `orgId`.
-- `.vercel/repo.json` — created by `vercel link --repo` (repo-based linking). Contains `orgId`, `remoteName`, and a `projects` array mapping directories to Vercel project IDs.
-
-Either file means the project is linked. Check for both.
-
-**Do NOT** use `vercel project inspect`, `vercel ls`, or `vercel link` to detect state in an unlinked directory — without a `.vercel/` config, they will interactively prompt (or with `--yes`, silently link as a side-effect). Only `vercel whoami` is safe to run anywhere.
-
-## Step 2: Choose a Deploy Method
-
-### Linked (`.vercel/` exists) + has git remote → Git Push
-
-This is the ideal state. The project is linked and has git integration.
-
-1. **Ask the user before pushing.** Never push without explicit approval:
-   ```
-   This project is connected to Vercel via git. I can commit and push to
-   trigger a deployment. Want me to proceed?
-   ```
-
-2. **Commit and push:**
-   ```bash
-   git add .
-   git commit -m "deploy: <description of changes>"
-   git push
-   ```
-   Vercel automatically builds from the push. Non-production branches get preview deployments; the production branch (usually `main`) gets a production deployment.
-
-3. **Retrieve the preview URL.** If the CLI is authenticated:
-   ```bash
-   sleep 5
-   vercel ls --format json
-   ```
-   The JSON output has a `deployments` array. Find the latest entry — its `url` field is the preview URL.
-
-   If the CLI is not authenticated, tell the user to check the Vercel dashboard or the commit status checks on their git provider for the preview URL.
-
----
-
-### Linked (`.vercel/` exists) + no git remote → `vercel deploy`
-
-The project is linked but there's no git repo. Deploy directly with the CLI.
-
-```bash
-vercel deploy [path] -y --no-wait
-```
-
-Use `--no-wait` so the CLI returns immediately with the deployment URL instead of blocking until the build finishes (builds can take a while). Then check on the deployment status with:
-
-```bash
-vercel inspect <deployment-url>
-```
-
-For production deploys (only if user explicitly asks):
-```bash
-vercel deploy [path] --prod -y --no-wait
-```
-
----
-
-### Not linked + CLI is authenticated → Link first, then deploy
-
-The CLI is working but the project isn't linked yet. This is the opportunity to get the user into the best state.
-
-1. **Ask the user which team to deploy to.** Present the team slugs from Step 1 as a bulleted list. If there's only one team (or just a personal account), skip this step.
-
-2. **Once a team is selected, proceed directly to linking.** Tell the user what will happen but do not ask for separate confirmation:
-   ```
-   Linking this project to <team name> on Vercel. This will create a Vercel
-   project to deploy to and enable automatic deployments on future git pushes.
-   ```
-
-3. **If a git remote exists**, use repo-based linking with the selected team scope:
-   ```bash
-   vercel link --repo --scope <team-slug>
-   ```
-   This reads the git remote URL and matches it to existing Vercel projects that deploy from that repo. It creates `.vercel/repo.json`. This is much more reliable than `vercel link` (without `--repo`), which tries to match by directory name and often fails when the local folder and Vercel project are named differently.
-
-   **If there is no git remote**, fall back to standard linking:
-   ```bash
-   vercel link --scope <team-slug>
-   ```
-   This prompts the user to select or create a project. It creates `.vercel/project.json`.
-
-4. **Then deploy using the best available method:**
-   - If a git remote exists → commit and push (see git push method above)
-   - If no git remote → `vercel deploy [path] -y --no-wait --scope <team-slug>`, then `vercel inspect <url>` to check status
-
----
-
-### Not linked + CLI not authenticated → Install, auth, link, deploy
-
-The Vercel CLI isn't set up at all.
-
-1. **Install the CLI (if not already installed):**
-   ```bash
-   npm install -g vercel
-   ```
-
-2. **Authenticate:**
-   ```bash
-   vercel login
-   ```
-   The user completes auth in their browser. If running in a non-interactive environment where login is not possible, skip to the **no-auth fallback** below.
-
-3. **Ask which team to deploy to** — present team slugs from `vercel teams list --format json` as a bulleted list. If only one team / personal account, skip. Once selected, proceed immediately.
-
-4. **Link the project** with the selected team scope (use `--repo` if a git remote exists, plain `vercel link` otherwise):
-   ```bash
-   vercel link --repo --scope <team-slug>   # if git remote exists
-   vercel link --scope <team-slug>          # if no git remote
-   ```
-
-5. **Deploy** using the best available method (git push if remote exists, otherwise `vercel deploy -y --no-wait --scope <team-slug>`, then `vercel inspect <url>` to check status).
-
----
-
-### No-Auth Fallback — claude.ai sandbox
-
-**When to use:** Last resort when the CLI can't be installed or authenticated in the claude.ai sandbox. This requires no authentication — it returns a **Preview URL** (live site) and a **Claim URL** (transfer to your Vercel account).
-
-```bash
-bash /mnt/skills/user/deploy-to-vercel/resources/deploy.sh [path]
-```
-
-**Arguments:**
-- `path` - Directory to deploy, or a `.tgz` file (defaults to current directory)
-
-**Examples:**
-```bash
-# Deploy current directory
-bash /mnt/skills/user/deploy-to-vercel/resources/deploy.sh
-
-# Deploy specific project
-bash /mnt/skills/user/deploy-to-vercel/resources/deploy.sh /path/to/project
-
-# Deploy existing tarball
-bash /mnt/skills/user/deploy-to-vercel/resources/deploy.sh /path/to/project.tgz
-```
-
-The script auto-detects the framework from `package.json`, packages the project (excluding `node_modules`, `.git`, `.env`), uploads it, and waits for the build to complete.
-
-**Tell the user:** "Your deployment is ready at [previewUrl]. Claim it at [claimUrl] to manage your deployment."
-
----
-
-### No-Auth Fallback — Codex sandbox
-
-**When to use:** In the Codex sandbox where the CLI may not be authenticated. Codex runs in a sandboxed environment by default — try the CLI first, and fall back to the deploy script if auth fails.
-
-1. **Check whether the Vercel CLI is installed** (no escalation needed for this check):
-   ```bash
-   command -v vercel
-   ```
-
-2. **If `vercel` is installed**, try deploying with the CLI:
-   ```bash
-   vercel deploy [path] -y --no-wait
-   ```
-
-3. **If `vercel` is not installed, or the CLI fails with "No existing credentials found"**, use the fallback script:
-   ```bash
-   skill_dir="<path-to-skill>"
-
-   # Deploy current directory
-   bash "$skill_dir/resources/deploy-codex.sh"
-
-   # Deploy specific project
-   bash "$skill_dir/resources/deploy-codex.sh" /path/to/project
-
-   # Deploy existing tarball
-   bash "$skill_dir/resources/deploy-codex.sh" /path/to/project.tgz
-   ```
-
-The script handles framework detection, packaging, and deployment. It waits for the build to complete and returns JSON with `previewUrl` and `claimUrl`.
-
-**Tell the user:** "Your deployment is ready at [previewUrl]. Claim it at [claimUrl] to manage your deployment."
-
-**Escalated network access:** Only escalate the actual deploy command if sandboxing blocks the network call (`sandbox_permissions=require_escalated`). Do **not** escalate the `command -v vercel` check.
-
----
-
-## Agent-Specific Notes
-
-### Claude Code / terminal-based agents
-
-You have full shell access. Do NOT use the `/mnt/skills/` path. Follow the decision flow above using the CLI directly.
-
-For the no-auth fallback, run the deploy script from the skill's installed location:
-```bash
-bash ~/.claude/skills/deploy-to-vercel/resources/deploy.sh [path]
-```
-The path may vary depending on where the user installed the skill.
-
-### Sandboxed environments (claude.ai)
-
-You likely cannot run `vercel login` or `git push`. Go directly to the **no-auth fallback — claude.ai sandbox**.
-
-### Codex
-
-Codex runs in a sandbox. Check if the CLI is available first, then fall back to the deploy script. Go to the **no-auth fallback — Codex sandbox**.
-
----
-
-## Output
-
-Always show the user the deployment URL.
-
-- **Git push:** Use `vercel ls --format json` to find the preview URL. If the CLI isn't authenticated, tell the user to check the Vercel dashboard or commit status checks.
-- **CLI deploy:** Show the URL returned by `vercel deploy --no-wait`. Use `vercel inspect <url>` to check build status and report it to the user.
-- **No-auth fallback:** Show both the preview URL and the claim URL:
-  ```
-  Deployment successful!
-
-  Preview URL: https://my-app-abc123.vercel.app
-  Claim URL:   https://vercel.com/claim-deployment?code=...
-
-  View your site at the Preview URL.
-  To transfer this deployment to your Vercel account, visit the Claim URL.
-  ```
-
-**Do not** curl or fetch the deployed URL to verify it works. Just return the link.
-
----
-
-## Troubleshooting
-
-### Network Egress Error (claude.ai)
-
-If deployment fails due to network restrictions on claude.ai, tell the user:
-
-```
-Deployment failed due to network restrictions. To fix this:
-
-1. Go to https://claude.ai/settings/capabilities
-2. Add *.vercel.com to the allowed domains
-3. Try deploying again
-```
-
-### Escalated Network Access (Codex / sandboxed environments)
-
-If deployment fails due to network issues (timeouts, DNS errors, connection resets) in a sandboxed environment, rerun with escalated permissions (`sandbox_permissions=require_escalated`). Do not escalate the initial CLI availability check — only escalate the actual deploy command.
-
-Example guidance to the user:
-```
-The deploy needs escalated network access to deploy to Vercel. I can rerun
-the command with escalated permissions — want me to proceed?
-```
-
-### CLI Auth Failure
-
-If `vercel login` or `vercel deploy` fails with authentication errors, fall back to the no-auth deploy script (claude.ai or Codex variant, depending on the environment).

+ 0 - 301
neo/.agents/skills/deploy-to-vercel/resources/deploy-codex.sh

@@ -1,301 +0,0 @@
-#!/bin/bash
-
-# Vercel Deployment Script for Codex (via claimable deploy endpoint)
-# Usage: ./deploy-codex.sh [project-path]
-# Returns: JSON with previewUrl, claimUrl, deploymentId, projectId
-
-set -euo pipefail
-
-DEPLOY_ENDPOINT="https://codex-deploy-skills.vercel.sh/api/deploy"
-
-# Detect framework from package.json
-detect_framework() {
-    local pkg_json="$1"
-
-    if [ ! -f "$pkg_json" ]; then
-        echo "null"
-        return
-    fi
-
-    local content=$(cat "$pkg_json")
-
-    # Helper to check if a package exists in dependencies or devDependencies.
-    # Use exact matching by default, with a separate prefix matcher for scoped
-    # package families like "@remix-run/".
-    has_dep_exact() {
-        echo "$content" | grep -q "\"$1\""
-    }
-
-    has_dep_prefix() {
-        echo "$content" | grep -q "\"$1"
-    }
-
-    # Order matters - check more specific frameworks first
-
-    # Blitz
-    if has_dep_exact "blitz"; then echo "blitzjs"; return; fi
-
-    # Next.js
-    if has_dep_exact "next"; then echo "nextjs"; return; fi
-
-    # Gatsby
-    if has_dep_exact "gatsby"; then echo "gatsby"; return; fi
-
-    # Remix
-    if has_dep_prefix "@remix-run/"; then echo "remix"; return; fi
-
-    # React Router (v7 framework mode)
-    if has_dep_prefix "@react-router/"; then echo "react-router"; return; fi
-
-    # TanStack Start
-    if has_dep_exact "@tanstack/start"; then echo "tanstack-start"; return; fi
-
-    # Astro
-    if has_dep_exact "astro"; then echo "astro"; return; fi
-
-    # Hydrogen (Shopify)
-    if has_dep_exact "@shopify/hydrogen"; then echo "hydrogen"; return; fi
-
-    # SvelteKit
-    if has_dep_exact "@sveltejs/kit"; then echo "sveltekit-1"; return; fi
-
-    # Svelte (standalone)
-    if has_dep_exact "svelte"; then echo "svelte"; return; fi
-
-    # Nuxt
-    if has_dep_exact "nuxt"; then echo "nuxtjs"; return; fi
-
-    # Vue with Vitepress
-    if has_dep_exact "vitepress"; then echo "vitepress"; return; fi
-
-    # Vue with Vuepress
-    if has_dep_exact "vuepress"; then echo "vuepress"; return; fi
-
-    # Gridsome
-    if has_dep_exact "gridsome"; then echo "gridsome"; return; fi
-
-    # SolidStart
-    if has_dep_exact "@solidjs/start"; then echo "solidstart-1"; return; fi
-
-    # Docusaurus
-    if has_dep_exact "@docusaurus/core"; then echo "docusaurus-2"; return; fi
-
-    # RedwoodJS
-    if has_dep_prefix "@redwoodjs/"; then echo "redwoodjs"; return; fi
-
-    # Hexo
-    if has_dep_exact "hexo"; then echo "hexo"; return; fi
-
-    # Eleventy
-    if has_dep_exact "@11ty/eleventy"; then echo "eleventy"; return; fi
-
-    # Angular / Ionic Angular
-    if has_dep_exact "@ionic/angular"; then echo "ionic-angular"; return; fi
-    if has_dep_exact "@angular/core"; then echo "angular"; return; fi
-
-    # Ionic React
-    if has_dep_exact "@ionic/react"; then echo "ionic-react"; return; fi
-
-    # Create React App
-    if has_dep_exact "react-scripts"; then echo "create-react-app"; return; fi
-
-    # Ember
-    if has_dep_exact "ember-cli" || has_dep_exact "ember-source"; then echo "ember"; return; fi
-
-    # Dojo
-    if has_dep_exact "@dojo/framework"; then echo "dojo"; return; fi
-
-    # Polymer
-    if has_dep_prefix "@polymer/"; then echo "polymer"; return; fi
-
-    # Preact
-    if has_dep_exact "preact"; then echo "preact"; return; fi
-
-    # Stencil
-    if has_dep_exact "@stencil/core"; then echo "stencil"; return; fi
-
-    # UmiJS
-    if has_dep_exact "umi"; then echo "umijs"; return; fi
-
-    # Sapper (legacy Svelte)
-    if has_dep_exact "sapper"; then echo "sapper"; return; fi
-
-    # Saber
-    if has_dep_exact "saber"; then echo "saber"; return; fi
-
-    # Sanity
-    if has_dep_exact "sanity"; then echo "sanity-v3"; return; fi
-    if has_dep_prefix "@sanity/"; then echo "sanity"; return; fi
-
-    # Storybook
-    if has_dep_prefix "@storybook/"; then echo "storybook"; return; fi
-
-    # NestJS
-    if has_dep_exact "@nestjs/core"; then echo "nestjs"; return; fi
-
-    # Elysia
-    if has_dep_exact "elysia"; then echo "elysia"; return; fi
-
-    # Hono
-    if has_dep_exact "hono"; then echo "hono"; return; fi
-
-    # Fastify
-    if has_dep_exact "fastify"; then echo "fastify"; return; fi
-
-    # h3
-    if has_dep_exact "h3"; then echo "h3"; return; fi
-
-    # Nitro
-    if has_dep_exact "nitropack"; then echo "nitro"; return; fi
-
-    # Express
-    if has_dep_exact "express"; then echo "express"; return; fi
-
-    # Vite (generic - check last among JS frameworks)
-    if has_dep_exact "vite"; then echo "vite"; return; fi
-
-    # Parcel
-    if has_dep_exact "parcel"; then echo "parcel"; return; fi
-
-    # No framework detected
-    echo "null"
-}
-
-# Parse arguments
-INPUT_PATH="${1:-.}"
-
-# Create temp directory for packaging
-TEMP_DIR=$(mktemp -d)
-TARBALL="$TEMP_DIR/project.tgz"
-STAGING_DIR="$TEMP_DIR/staging"
-CLEANUP_TEMP=true
-
-cleanup() {
-    if [ "$CLEANUP_TEMP" = true ]; then
-        rm -rf "$TEMP_DIR"
-    fi
-}
-trap cleanup EXIT
-
-echo "Preparing deployment..." >&2
-
-# Check if input is a .tgz file or a directory
-FRAMEWORK="null"
-
-if [ -f "$INPUT_PATH" ] && [[ "$INPUT_PATH" == *.tgz ]]; then
-    # Input is already a tarball, use it directly
-    echo "Using provided tarball..." >&2
-    TARBALL="$INPUT_PATH"
-    CLEANUP_TEMP=false
-    # Can't detect framework from tarball, leave as null
-elif [ -d "$INPUT_PATH" ]; then
-    # Input is a directory, need to tar it
-    PROJECT_PATH=$(cd "$INPUT_PATH" && pwd)
-
-    # Detect framework from package.json
-    FRAMEWORK=$(detect_framework "$PROJECT_PATH/package.json")
-
-    # Stage files into a temporary directory to avoid mutating the source tree.
-    mkdir -p "$STAGING_DIR"
-    echo "Staging project files..." >&2
-    tar -C "$PROJECT_PATH" \
-        --exclude='node_modules' \
-        --exclude='.git' \
-        --exclude='.env' \
-        --exclude='.env.*' \
-        -cf - . | tar -C "$STAGING_DIR" -xf -
-
-    # Check if this is a static HTML project (no package.json)
-    if [ ! -f "$PROJECT_PATH/package.json" ]; then
-        # Find HTML files in root
-        HTML_FILES=$(find "$STAGING_DIR" -maxdepth 1 -name "*.html" -type f)
-        HTML_COUNT=$(printf '%s\n' "$HTML_FILES" | sed '/^$/d' | wc -l | tr -d '[:space:]')
-
-        # If there's exactly one HTML file and it's not index.html, rename it
-        if [ "$HTML_COUNT" -eq 1 ]; then
-            HTML_FILE=$(echo "$HTML_FILES" | head -1)
-            BASENAME=$(basename "$HTML_FILE")
-            if [ "$BASENAME" != "index.html" ]; then
-                echo "Renaming $BASENAME to index.html..." >&2
-                mv "$HTML_FILE" "$STAGING_DIR/index.html"
-            fi
-        fi
-    fi
-
-    # Create tarball from the staging directory
-    echo "Creating deployment package..." >&2
-    tar -czf "$TARBALL" -C "$STAGING_DIR" .
-else
-    echo "Error: Input must be a directory or a .tgz file" >&2
-    exit 1
-fi
-
-if [ "$FRAMEWORK" != "null" ]; then
-    echo "Detected framework: $FRAMEWORK" >&2
-fi
-
-# Deploy
-echo "Deploying..." >&2
-RESPONSE=$(curl -s -X POST "$DEPLOY_ENDPOINT" -F "file=@$TARBALL" -F "framework=$FRAMEWORK")
-
-# Check for error in response
-if echo "$RESPONSE" | grep -q '"error"'; then
-    ERROR_MSG=$(echo "$RESPONSE" | grep -o '"error":"[^"]*"' | cut -d'"' -f4)
-    echo "Error: $ERROR_MSG" >&2
-    exit 1
-fi
-
-# Extract URLs from response
-PREVIEW_URL=$(echo "$RESPONSE" | grep -o '"previewUrl":"[^"]*"' | cut -d'"' -f4)
-CLAIM_URL=$(echo "$RESPONSE" | grep -o '"claimUrl":"[^"]*"' | cut -d'"' -f4)
-
-if [ -z "$PREVIEW_URL" ]; then
-    echo "Error: Could not extract preview URL from response" >&2
-    echo "$RESPONSE" >&2
-    exit 1
-fi
-
-echo "Deployment started. Waiting for build to complete..." >&2
-echo "Preview URL: $PREVIEW_URL" >&2
-
-# Poll the preview URL until it returns a non-5xx response (5xx = still building)
-MAX_ATTEMPTS=60  # 5 minutes max (60 * 5 seconds)
-ATTEMPT=0
-
-while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
-    HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$PREVIEW_URL")
-
-    if [ "$HTTP_STATUS" -eq 200 ]; then
-        echo "" >&2
-        echo "Deployment ready!" >&2
-        break
-    elif [ "$HTTP_STATUS" -ge 500 ]; then
-        # 5xx means still building/deploying
-        echo "Building... (attempt $((ATTEMPT + 1))/$MAX_ATTEMPTS)" >&2
-        sleep 5
-        ATTEMPT=$((ATTEMPT + 1))
-    elif [ "$HTTP_STATUS" -ge 400 ] && [ "$HTTP_STATUS" -lt 500 ]; then
-        # 4xx might be an error or the app itself returns 4xx - it's responding
-        echo "" >&2
-        echo "Deployment ready (returned $HTTP_STATUS)!" >&2
-        break
-    else
-        # Any other status, assume it's ready
-        echo "" >&2
-        echo "Deployment ready!" >&2
-        break
-    fi
-done
-
-if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
-    echo "" >&2
-    echo "Warning: Timed out waiting for deployment, but it may still be building." >&2
-fi
-
-echo "" >&2
-echo "Preview URL: $PREVIEW_URL" >&2
-echo "Claim URL:   $CLAIM_URL" >&2
-echo "" >&2
-
-# Output JSON for programmatic use
-echo "$RESPONSE"

+ 0 - 301
neo/.agents/skills/deploy-to-vercel/resources/deploy.sh

@@ -1,301 +0,0 @@
-#!/bin/bash
-
-# Vercel Deployment Script (via claimable deploy endpoint)
-# Usage: ./deploy.sh [project-path]
-# Returns: JSON with previewUrl, claimUrl, deploymentId, projectId
-
-set -euo pipefail
-
-DEPLOY_ENDPOINT="https://claude-skills-deploy.vercel.com/api/deploy"
-
-# Detect framework from package.json
-detect_framework() {
-    local pkg_json="$1"
-
-    if [ ! -f "$pkg_json" ]; then
-        echo "null"
-        return
-    fi
-
-    local content=$(cat "$pkg_json")
-
-    # Helper to check if a package exists in dependencies or devDependencies.
-    # Use exact matching by default, with a separate prefix matcher for scoped
-    # package families like "@remix-run/".
-    has_dep_exact() {
-        echo "$content" | grep -q "\"$1\""
-    }
-
-    has_dep_prefix() {
-        echo "$content" | grep -q "\"$1"
-    }
-
-    # Order matters - check more specific frameworks first
-
-    # Blitz
-    if has_dep_exact "blitz"; then echo "blitzjs"; return; fi
-
-    # Next.js
-    if has_dep_exact "next"; then echo "nextjs"; return; fi
-
-    # Gatsby
-    if has_dep_exact "gatsby"; then echo "gatsby"; return; fi
-
-    # Remix
-    if has_dep_prefix "@remix-run/"; then echo "remix"; return; fi
-
-    # React Router (v7 framework mode)
-    if has_dep_prefix "@react-router/"; then echo "react-router"; return; fi
-
-    # TanStack Start
-    if has_dep_exact "@tanstack/start"; then echo "tanstack-start"; return; fi
-
-    # Astro
-    if has_dep_exact "astro"; then echo "astro"; return; fi
-
-    # Hydrogen (Shopify)
-    if has_dep_exact "@shopify/hydrogen"; then echo "hydrogen"; return; fi
-
-    # SvelteKit
-    if has_dep_exact "@sveltejs/kit"; then echo "sveltekit-1"; return; fi
-
-    # Svelte (standalone)
-    if has_dep_exact "svelte"; then echo "svelte"; return; fi
-
-    # Nuxt
-    if has_dep_exact "nuxt"; then echo "nuxtjs"; return; fi
-
-    # Vue with Vitepress
-    if has_dep_exact "vitepress"; then echo "vitepress"; return; fi
-
-    # Vue with Vuepress
-    if has_dep_exact "vuepress"; then echo "vuepress"; return; fi
-
-    # Gridsome
-    if has_dep_exact "gridsome"; then echo "gridsome"; return; fi
-
-    # SolidStart
-    if has_dep_exact "@solidjs/start"; then echo "solidstart-1"; return; fi
-
-    # Docusaurus
-    if has_dep_exact "@docusaurus/core"; then echo "docusaurus-2"; return; fi
-
-    # RedwoodJS
-    if has_dep_prefix "@redwoodjs/"; then echo "redwoodjs"; return; fi
-
-    # Hexo
-    if has_dep_exact "hexo"; then echo "hexo"; return; fi
-
-    # Eleventy
-    if has_dep_exact "@11ty/eleventy"; then echo "eleventy"; return; fi
-
-    # Angular / Ionic Angular
-    if has_dep_exact "@ionic/angular"; then echo "ionic-angular"; return; fi
-    if has_dep_exact "@angular/core"; then echo "angular"; return; fi
-
-    # Ionic React
-    if has_dep_exact "@ionic/react"; then echo "ionic-react"; return; fi
-
-    # Create React App
-    if has_dep_exact "react-scripts"; then echo "create-react-app"; return; fi
-
-    # Ember
-    if has_dep_exact "ember-cli" || has_dep_exact "ember-source"; then echo "ember"; return; fi
-
-    # Dojo
-    if has_dep_exact "@dojo/framework"; then echo "dojo"; return; fi
-
-    # Polymer
-    if has_dep_prefix "@polymer/"; then echo "polymer"; return; fi
-
-    # Preact
-    if has_dep_exact "preact"; then echo "preact"; return; fi
-
-    # Stencil
-    if has_dep_exact "@stencil/core"; then echo "stencil"; return; fi
-
-    # UmiJS
-    if has_dep_exact "umi"; then echo "umijs"; return; fi
-
-    # Sapper (legacy Svelte)
-    if has_dep_exact "sapper"; then echo "sapper"; return; fi
-
-    # Saber
-    if has_dep_exact "saber"; then echo "saber"; return; fi
-
-    # Sanity
-    if has_dep_exact "sanity"; then echo "sanity-v3"; return; fi
-    if has_dep_prefix "@sanity/"; then echo "sanity"; return; fi
-
-    # Storybook
-    if has_dep_prefix "@storybook/"; then echo "storybook"; return; fi
-
-    # NestJS
-    if has_dep_exact "@nestjs/core"; then echo "nestjs"; return; fi
-
-    # Elysia
-    if has_dep_exact "elysia"; then echo "elysia"; return; fi
-
-    # Hono
-    if has_dep_exact "hono"; then echo "hono"; return; fi
-
-    # Fastify
-    if has_dep_exact "fastify"; then echo "fastify"; return; fi
-
-    # h3
-    if has_dep_exact "h3"; then echo "h3"; return; fi
-
-    # Nitro
-    if has_dep_exact "nitropack"; then echo "nitro"; return; fi
-
-    # Express
-    if has_dep_exact "express"; then echo "express"; return; fi
-
-    # Vite (generic - check last among JS frameworks)
-    if has_dep_exact "vite"; then echo "vite"; return; fi
-
-    # Parcel
-    if has_dep_exact "parcel"; then echo "parcel"; return; fi
-
-    # No framework detected
-    echo "null"
-}
-
-# Parse arguments
-INPUT_PATH="${1:-.}"
-
-# Create temp directory for packaging
-TEMP_DIR=$(mktemp -d)
-TARBALL="$TEMP_DIR/project.tgz"
-STAGING_DIR="$TEMP_DIR/staging"
-CLEANUP_TEMP=true
-
-cleanup() {
-    if [ "$CLEANUP_TEMP" = true ]; then
-        rm -rf "$TEMP_DIR"
-    fi
-}
-trap cleanup EXIT
-
-echo "Preparing deployment..." >&2
-
-# Check if input is a .tgz file or a directory
-FRAMEWORK="null"
-
-if [ -f "$INPUT_PATH" ] && [[ "$INPUT_PATH" == *.tgz ]]; then
-    # Input is already a tarball, use it directly
-    echo "Using provided tarball..." >&2
-    TARBALL="$INPUT_PATH"
-    CLEANUP_TEMP=false
-    # Can't detect framework from tarball, leave as null
-elif [ -d "$INPUT_PATH" ]; then
-    # Input is a directory, need to tar it
-    PROJECT_PATH=$(cd "$INPUT_PATH" && pwd)
-
-    # Detect framework from package.json
-    FRAMEWORK=$(detect_framework "$PROJECT_PATH/package.json")
-
-    # Stage files into a temporary directory to avoid mutating the source tree.
-    mkdir -p "$STAGING_DIR"
-    echo "Staging project files..." >&2
-    tar -C "$PROJECT_PATH" \
-        --exclude='node_modules' \
-        --exclude='.git' \
-        --exclude='.env' \
-        --exclude='.env.*' \
-        -cf - . | tar -C "$STAGING_DIR" -xf -
-
-    # Check if this is a static HTML project (no package.json)
-    if [ ! -f "$PROJECT_PATH/package.json" ]; then
-        # Find HTML files in root
-        HTML_FILES=$(find "$STAGING_DIR" -maxdepth 1 -name "*.html" -type f)
-        HTML_COUNT=$(printf '%s\n' "$HTML_FILES" | sed '/^$/d' | wc -l | tr -d '[:space:]')
-
-        # If there's exactly one HTML file and it's not index.html, rename it
-        if [ "$HTML_COUNT" -eq 1 ]; then
-            HTML_FILE=$(echo "$HTML_FILES" | head -1)
-            BASENAME=$(basename "$HTML_FILE")
-            if [ "$BASENAME" != "index.html" ]; then
-                echo "Renaming $BASENAME to index.html..." >&2
-                mv "$HTML_FILE" "$STAGING_DIR/index.html"
-            fi
-        fi
-    fi
-
-    # Create tarball from the staging directory
-    echo "Creating deployment package..." >&2
-    tar -czf "$TARBALL" -C "$STAGING_DIR" .
-else
-    echo "Error: Input must be a directory or a .tgz file" >&2
-    exit 1
-fi
-
-if [ "$FRAMEWORK" != "null" ]; then
-    echo "Detected framework: $FRAMEWORK" >&2
-fi
-
-# Deploy
-echo "Deploying..." >&2
-RESPONSE=$(curl -s -X POST "$DEPLOY_ENDPOINT" -F "file=@$TARBALL" -F "framework=$FRAMEWORK")
-
-# Check for error in response
-if echo "$RESPONSE" | grep -q '"error"'; then
-    ERROR_MSG=$(echo "$RESPONSE" | grep -o '"error":"[^"]*"' | cut -d'"' -f4)
-    echo "Error: $ERROR_MSG" >&2
-    exit 1
-fi
-
-# Extract URLs from response
-PREVIEW_URL=$(echo "$RESPONSE" | grep -o '"previewUrl":"[^"]*"' | cut -d'"' -f4)
-CLAIM_URL=$(echo "$RESPONSE" | grep -o '"claimUrl":"[^"]*"' | cut -d'"' -f4)
-
-if [ -z "$PREVIEW_URL" ]; then
-    echo "Error: Could not extract preview URL from response" >&2
-    echo "$RESPONSE" >&2
-    exit 1
-fi
-
-echo "Deployment started. Waiting for build to complete..." >&2
-echo "Preview URL: $PREVIEW_URL" >&2
-
-# Poll the preview URL until it returns a non-5xx response (5xx = still building)
-MAX_ATTEMPTS=60  # 5 minutes max (60 * 5 seconds)
-ATTEMPT=0
-
-while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
-    HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$PREVIEW_URL")
-
-    if [ "$HTTP_STATUS" -eq 200 ]; then
-        echo "" >&2
-        echo "Deployment ready!" >&2
-        break
-    elif [ "$HTTP_STATUS" -ge 500 ]; then
-        # 5xx means still building/deploying
-        echo "Building... (attempt $((ATTEMPT + 1))/$MAX_ATTEMPTS)" >&2
-        sleep 5
-        ATTEMPT=$((ATTEMPT + 1))
-    elif [ "$HTTP_STATUS" -ge 400 ] && [ "$HTTP_STATUS" -lt 500 ]; then
-        # 4xx might be an error or the app itself returns 4xx - it's responding
-        echo "" >&2
-        echo "Deployment ready (returned $HTTP_STATUS)!" >&2
-        break
-    else
-        # Any other status, assume it's ready
-        echo "" >&2
-        echo "Deployment ready!" >&2
-        break
-    fi
-done
-
-if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
-    echo "" >&2
-    echo "Warning: Timed out waiting for deployment, but it may still be building." >&2
-fi
-
-echo "" >&2
-echo "Preview URL: $PREVIEW_URL" >&2
-echo "Claim URL:   $CLAIM_URL" >&2
-echo "" >&2
-
-# Output JSON for programmatic use
-echo "$RESPONSE"

+ 0 - 153
neo/.agents/skills/next-best-practices/SKILL.md

@@ -1,153 +0,0 @@
----
-name: next-best-practices
-description: Next.js best practices - file conventions, RSC boundaries, data patterns, async APIs, metadata, error handling, route handlers, image/font optimization, bundling
-user-invocable: false
----
-
-# Next.js Best Practices
-
-Apply these rules when writing or reviewing Next.js code.
-
-## File Conventions
-
-See [file-conventions.md](./file-conventions.md) for:
-- Project structure and special files
-- Route segments (dynamic, catch-all, groups)
-- Parallel and intercepting routes
-- Middleware rename in v16 (middleware → proxy)
-
-## RSC Boundaries
-
-Detect invalid React Server Component patterns.
-
-See [rsc-boundaries.md](./rsc-boundaries.md) for:
-- Async client component detection (invalid)
-- Non-serializable props detection
-- Server Action exceptions
-
-## Async Patterns
-
-Next.js 15+ async API changes.
-
-See [async-patterns.md](./async-patterns.md) for:
-- Async `params` and `searchParams`
-- Async `cookies()` and `headers()`
-- Migration codemod
-
-## Runtime Selection
-
-See [runtime-selection.md](./runtime-selection.md) for:
-- Default to Node.js runtime
-- When Edge runtime is appropriate
-
-## Directives
-
-See [directives.md](./directives.md) for:
-- `'use client'`, `'use server'` (React)
-- `'use cache'` (Next.js)
-
-## Functions
-
-See [functions.md](./functions.md) for:
-- Navigation hooks: `useRouter`, `usePathname`, `useSearchParams`, `useParams`
-- Server functions: `cookies`, `headers`, `draftMode`, `after`
-- Generate functions: `generateStaticParams`, `generateMetadata`
-
-## Error Handling
-
-See [error-handling.md](./error-handling.md) for:
-- `error.tsx`, `global-error.tsx`, `not-found.tsx`
-- `redirect`, `permanentRedirect`, `notFound`
-- `forbidden`, `unauthorized` (auth errors)
-- `unstable_rethrow` for catch blocks
-
-## Data Patterns
-
-See [data-patterns.md](./data-patterns.md) for:
-- Server Components vs Server Actions vs Route Handlers
-- Avoiding data waterfalls (`Promise.all`, Suspense, preload)
-- Client component data fetching
-
-## Route Handlers
-
-See [route-handlers.md](./route-handlers.md) for:
-- `route.ts` basics
-- GET handler conflicts with `page.tsx`
-- Environment behavior (no React DOM)
-- When to use vs Server Actions
-
-## Metadata & OG Images
-
-See [metadata.md](./metadata.md) for:
-- Static and dynamic metadata
-- `generateMetadata` function
-- OG image generation with `next/og`
-- File-based metadata conventions
-
-## Image Optimization
-
-See [image.md](./image.md) for:
-- Always use `next/image` over `<img>`
-- Remote images configuration
-- Responsive `sizes` attribute
-- Blur placeholders
-- Priority loading for LCP
-
-## Font Optimization
-
-See [font.md](./font.md) for:
-- `next/font` setup
-- Google Fonts, local fonts
-- Tailwind CSS integration
-- Preloading subsets
-
-## Bundling
-
-See [bundling.md](./bundling.md) for:
-- Server-incompatible packages
-- CSS imports (not link tags)
-- Polyfills (already included)
-- ESM/CommonJS issues
-- Bundle analysis
-
-## Scripts
-
-See [scripts.md](./scripts.md) for:
-- `next/script` vs native script tags
-- Inline scripts need `id`
-- Loading strategies
-- Google Analytics with `@next/third-parties`
-
-## Hydration Errors
-
-See [hydration-error.md](./hydration-error.md) for:
-- Common causes (browser APIs, dates, invalid HTML)
-- Debugging with error overlay
-- Fixes for each cause
-
-## Suspense Boundaries
-
-See [suspense-boundaries.md](./suspense-boundaries.md) for:
-- CSR bailout with `useSearchParams` and `usePathname`
-- Which hooks require Suspense boundaries
-
-## Parallel & Intercepting Routes
-
-See [parallel-routes.md](./parallel-routes.md) for:
-- Modal patterns with `@slot` and `(.)` interceptors
-- `default.tsx` for fallbacks
-- Closing modals correctly with `router.back()`
-
-## Self-Hosting
-
-See [self-hosting.md](./self-hosting.md) for:
-- `output: 'standalone'` for Docker
-- Cache handlers for multi-instance ISR
-- What works vs needs extra setup
-
-## Debug Tricks
-
-See [debug-tricks.md](./debug-tricks.md) for:
-- MCP endpoint for AI-assisted debugging
-- Rebuild specific routes with `--debug-build-paths`
-

+ 0 - 87
neo/.agents/skills/next-best-practices/async-patterns.md

@@ -1,87 +0,0 @@
-# Async Patterns
-
-In Next.js 15+, `params`, `searchParams`, `cookies()`, and `headers()` are asynchronous.
-
-## Async Params and SearchParams
-
-Always type them as `Promise<...>` and await them.
-
-### Pages and Layouts
-
-```tsx
-type Props = { params: Promise<{ slug: string }> }
-
-export default async function Page({ params }: Props) {
-  const { slug } = await params
-}
-```
-
-### Route Handlers
-
-```tsx
-export async function GET(
-  request: Request,
-  { params }: { params: Promise<{ id: string }> }
-) {
-  const { id } = await params
-}
-```
-
-### SearchParams
-
-```tsx
-type Props = {
-  params: Promise<{ slug: string }>
-  searchParams: Promise<{ query?: string }>
-}
-
-export default async function Page({ params, searchParams }: Props) {
-  const { slug } = await params
-  const { query } = await searchParams
-}
-```
-
-### Synchronous Components
-
-Use `React.use()` for non-async components:
-
-```tsx
-import { use } from 'react'
-
-type Props = { params: Promise<{ slug: string }> }
-
-export default function Page({ params }: Props) {
-  const { slug } = use(params)
-}
-```
-
-### generateMetadata
-
-```tsx
-type Props = { params: Promise<{ slug: string }> }
-
-export async function generateMetadata({ params }: Props): Promise<Metadata> {
-  const { slug } = await params
-  return { title: slug }
-}
-```
-
-## Async Cookies and Headers
-
-```tsx
-import { cookies, headers } from 'next/headers'
-
-export default async function Page() {
-  const cookieStore = await cookies()
-  const headersList = await headers()
-
-  const theme = cookieStore.get('theme')
-  const userAgent = headersList.get('user-agent')
-}
-```
-
-## Migration Codemod
-
-```bash
-npx @next/codemod@latest next-async-request-api .
-```

+ 0 - 180
neo/.agents/skills/next-best-practices/bundling.md

@@ -1,180 +0,0 @@
-# Bundling
-
-Fix common bundling issues with third-party packages.
-
-## Server-Incompatible Packages
-
-Some packages use browser APIs (`window`, `document`, `localStorage`) and fail in Server Components.
-
-### Error Signs
-
-```
-ReferenceError: window is not defined
-ReferenceError: document is not defined
-ReferenceError: localStorage is not defined
-Module not found: Can't resolve 'fs'
-```
-
-### Solution 1: Mark as Client-Only
-
-If the package is only needed on client:
-
-```tsx
-// Bad: Fails - package uses window
-import SomeChart from 'some-chart-library'
-
-export default function Page() {
-  return <SomeChart />
-}
-
-// Good: Use dynamic import with ssr: false
-import dynamic from 'next/dynamic'
-
-const SomeChart = dynamic(() => import('some-chart-library'), {
-  ssr: false,
-})
-
-export default function Page() {
-  return <SomeChart />
-}
-```
-
-### Solution 2: Externalize from Server Bundle
-
-For packages that should run on server but have bundling issues:
-
-```js
-// next.config.js
-module.exports = {
-  serverExternalPackages: ['problematic-package'],
-}
-```
-
-Use this for:
-- Packages with native bindings (sharp, bcrypt)
-- Packages that don't bundle well (some ORMs)
-- Packages with circular dependencies
-
-### Solution 3: Client Component Wrapper
-
-Wrap the entire usage in a client component:
-
-```tsx
-// components/ChartWrapper.tsx
-'use client'
-
-import { Chart } from 'chart-library'
-
-export function ChartWrapper(props) {
-  return <Chart {...props} />
-}
-
-// app/page.tsx (server component)
-import { ChartWrapper } from '@/components/ChartWrapper'
-
-export default function Page() {
-  return <ChartWrapper data={data} />
-}
-```
-
-## CSS Imports
-
-Import CSS files instead of using `<link>` tags. Next.js handles bundling and optimization.
-
-```tsx
-// Bad: Manual link tag
-<link rel="stylesheet" href="/styles.css" />
-
-// Good: Import CSS
-import './styles.css'
-
-// Good: CSS Modules
-import styles from './Button.module.css'
-```
-
-## Polyfills
-
-Next.js includes common polyfills automatically. Don't load redundant ones from polyfill.io or similar CDNs.
-
-Already included: `Array.from`, `Object.assign`, `Promise`, `fetch`, `Map`, `Set`, `Symbol`, `URLSearchParams`, and 50+ others.
-
-```tsx
-// Bad: Redundant polyfills
-<script src="https://polyfill.io/v3/polyfill.min.js?features=fetch,Promise,Array.from" />
-
-// Good: Next.js includes these automatically
-```
-
-## ESM/CommonJS Issues
-
-### Error Signs
-
-```
-SyntaxError: Cannot use import statement outside a module
-Error: require() of ES Module
-Module not found: ESM packages need to be imported
-```
-
-### Solution: Transpile Package
-
-```js
-// next.config.js
-module.exports = {
-  transpilePackages: ['some-esm-package', 'another-package'],
-}
-```
-
-## Common Problematic Packages
-
-| Package | Issue | Solution |
-|---------|-------|----------|
-| `sharp` | Native bindings | `serverExternalPackages: ['sharp']` |
-| `bcrypt` | Native bindings | `serverExternalPackages: ['bcrypt']` or use `bcryptjs` |
-| `canvas` | Native bindings | `serverExternalPackages: ['canvas']` |
-| `recharts` | Uses window | `dynamic(() => import('recharts'), { ssr: false })` |
-| `react-quill` | Uses document | `dynamic(() => import('react-quill'), { ssr: false })` |
-| `mapbox-gl` | Uses window | `dynamic(() => import('mapbox-gl'), { ssr: false })` |
-| `monaco-editor` | Uses window | `dynamic(() => import('@monaco-editor/react'), { ssr: false })` |
-| `lottie-web` | Uses document | `dynamic(() => import('lottie-react'), { ssr: false })` |
-
-## Bundle Analysis
-
-Analyze bundle size with the built-in analyzer (Next.js 16.1+):
-
-```bash
-next experimental-analyze
-```
-
-This opens an interactive UI to:
-- Filter by route, environment (client/server), and type
-- Inspect module sizes and import chains
-- View treemap visualization
-
-Save output for comparison:
-
-```bash
-next experimental-analyze --output
-# Output saved to .next/diagnostics/analyze
-```
-
-Reference: https://nextjs.org/docs/app/guides/package-bundling
-
-## Migrating from Webpack to Turbopack
-
-Turbopack is the default bundler in Next.js 15+. If you have custom webpack config, migrate to Turbopack-compatible alternatives:
-
-```js
-// next.config.js
-module.exports = {
-  // Good: Works with Turbopack
-  serverExternalPackages: ['package'],
-  transpilePackages: ['package'],
-
-  // Bad: Webpack-only - migrate away from this
-  webpack: (config) => {
-    // custom webpack config
-  },
-}
-```
-
-Reference: https://nextjs.org/docs/app/building-your-application/upgrading/from-webpack-to-turbopack

+ 0 - 297
neo/.agents/skills/next-best-practices/data-patterns.md

@@ -1,297 +0,0 @@
-# Data Patterns
-
-Choose the right data fetching pattern for each use case.
-
-## Decision Tree
-
-```
-Need to fetch data?
-├── From a Server Component?
-│   └── Use: Fetch directly (no API needed)
-│
-├── From a Client Component?
-│   ├── Is it a mutation (POST/PUT/DELETE)?
-│   │   └── Use: Server Action
-│   └── Is it a read (GET)?
-│       └── Use: Route Handler OR pass from Server Component
-│
-├── Need external API access (webhooks, third parties)?
-│   └── Use: Route Handler
-│
-└── Need REST API for mobile app / external clients?
-    └── Use: Route Handler
-```
-
-## Pattern 1: Server Components (Preferred for Reads)
-
-Fetch data directly in Server Components - no API layer needed.
-
-```tsx
-// app/users/page.tsx
-async function UsersPage() {
-  // Direct database access - no API round-trip
-  const users = await db.user.findMany();
-
-  // Or fetch from external API
-  const posts = await fetch('https://api.example.com/posts').then(r => r.json());
-
-  return (
-    <ul>
-      {users.map(user => <li key={user.id}>{user.name}</li>)}
-    </ul>
-  );
-}
-```
-
-**Benefits**:
-- No API to maintain
-- No client-server waterfall
-- Secrets stay on server
-- Direct database access
-
-## Pattern 2: Server Actions (Preferred for Mutations)
-
-Server Actions are the recommended way to handle mutations.
-
-```tsx
-// app/actions.ts
-'use server';
-
-import { revalidatePath } from 'next/cache';
-
-export async function createPost(formData: FormData) {
-  const title = formData.get('title') as string;
-
-  await db.post.create({ data: { title } });
-
-  revalidatePath('/posts');
-}
-
-export async function deletePost(id: string) {
-  await db.post.delete({ where: { id } });
-
-  revalidateTag('posts');
-}
-```
-
-```tsx
-// app/posts/new/page.tsx
-import { createPost } from '@/app/actions';
-
-export default function NewPost() {
-  return (
-    <form action={createPost}>
-      <input name="title" required />
-      <button type="submit">Create</button>
-    </form>
-  );
-}
-```
-
-**Benefits**:
-- End-to-end type safety
-- Progressive enhancement (works without JS)
-- Automatic request handling
-- Integrated with React transitions
-
-**Constraints**:
-- POST only (no GET caching semantics)
-- Internal use only (no external access)
-- Cannot return non-serializable data
-
-## Pattern 3: Route Handlers (APIs)
-
-Use Route Handlers when you need a REST API.
-
-```tsx
-// app/api/posts/route.ts
-import { NextRequest, NextResponse } from 'next/server';
-
-// GET is cacheable
-export async function GET(request: NextRequest) {
-  const posts = await db.post.findMany();
-  return NextResponse.json(posts);
-}
-
-// POST for mutations
-export async function POST(request: NextRequest) {
-  const body = await request.json();
-  const post = await db.post.create({ data: body });
-  return NextResponse.json(post, { status: 201 });
-}
-```
-
-**When to use**:
-- External API access (mobile apps, third parties)
-- Webhooks from external services
-- GET endpoints that need HTTP caching
-- OpenAPI/Swagger documentation needed
-
-**When NOT to use**:
-- Internal data fetching (use Server Components)
-- Mutations from your UI (use Server Actions)
-
-## Avoiding Data Waterfalls
-
-### Problem: Sequential Fetches
-
-```tsx
-// Bad: Sequential waterfalls
-async function Dashboard() {
-  const user = await getUser();        // Wait...
-  const posts = await getPosts();      // Then wait...
-  const comments = await getComments(); // Then wait...
-
-  return <div>...</div>;
-}
-```
-
-### Solution 1: Parallel Fetching with Promise.all
-
-```tsx
-// Good: Parallel fetching
-async function Dashboard() {
-  const [user, posts, comments] = await Promise.all([
-    getUser(),
-    getPosts(),
-    getComments(),
-  ]);
-
-  return <div>...</div>;
-}
-```
-
-### Solution 2: Streaming with Suspense
-
-```tsx
-// Good: Show content progressively
-import { Suspense } from 'react';
-
-async function Dashboard() {
-  return (
-    <div>
-      <Suspense fallback={<UserSkeleton />}>
-        <UserSection />
-      </Suspense>
-      <Suspense fallback={<PostsSkeleton />}>
-        <PostsSection />
-      </Suspense>
-    </div>
-  );
-}
-
-async function UserSection() {
-  const user = await getUser(); // Fetches independently
-  return <div>{user.name}</div>;
-}
-
-async function PostsSection() {
-  const posts = await getPosts(); // Fetches independently
-  return <PostList posts={posts} />;
-}
-```
-
-### Solution 3: Preload Pattern
-
-```tsx
-// lib/data.ts
-import { cache } from 'react';
-
-export const getUser = cache(async (id: string) => {
-  return db.user.findUnique({ where: { id } });
-});
-
-export const preloadUser = (id: string) => {
-  void getUser(id); // Fire and forget
-};
-```
-
-```tsx
-// app/user/[id]/page.tsx
-import { getUser, preloadUser } from '@/lib/data';
-
-export default async function UserPage({ params }) {
-  const { id } = await params;
-
-  // Start fetching early
-  preloadUser(id);
-
-  // Do other work...
-
-  // Data likely ready by now
-  const user = await getUser(id);
-  return <div>{user.name}</div>;
-}
-```
-
-## Client Component Data Fetching
-
-When Client Components need data:
-
-### Option 1: Pass from Server Component (Preferred)
-
-```tsx
-// Server Component
-async function Page() {
-  const data = await fetchData();
-  return <ClientComponent initialData={data} />;
-}
-
-// Client Component
-'use client';
-function ClientComponent({ initialData }) {
-  const [data, setData] = useState(initialData);
-  // ...
-}
-```
-
-### Option 2: Fetch on Mount (When Necessary)
-
-```tsx
-'use client';
-import { useEffect, useState } from 'react';
-
-function ClientComponent() {
-  const [data, setData] = useState(null);
-
-  useEffect(() => {
-    fetch('/api/data')
-      .then(r => r.json())
-      .then(setData);
-  }, []);
-
-  if (!data) return <Loading />;
-  return <div>{data.value}</div>;
-}
-```
-
-### Option 3: Server Action for Reads (Works But Not Ideal)
-
-Server Actions can be called from Client Components for reads, but this is not their intended purpose:
-
-```tsx
-'use client';
-import { getData } from './actions';
-import { useEffect, useState } from 'react';
-
-function ClientComponent() {
-  const [data, setData] = useState(null);
-
-  useEffect(() => {
-    getData().then(setData);
-  }, []);
-
-  return <div>{data?.value}</div>;
-}
-```
-
-**Note**: Server Actions always use POST, so no HTTP caching. Prefer Route Handlers for cacheable reads.
-
-## Quick Reference
-
-| Pattern | Use Case | HTTP Method | Caching |
-|---------|----------|-------------|---------|
-| Server Component fetch | Internal reads | Any | Full Next.js caching |
-| Server Action | Mutations, form submissions | POST only | No |
-| Route Handler | External APIs, webhooks | Any | GET can be cached |
-| Client fetch to API | Client-side reads | Any | HTTP cache headers |

+ 0 - 105
neo/.agents/skills/next-best-practices/debug-tricks.md

@@ -1,105 +0,0 @@
-# Debug Tricks
-
-Tricks to speed up debugging Next.js applications.
-
-## MCP Endpoint (Dev Server)
-
-Next.js exposes a `/_next/mcp` endpoint in development for AI-assisted debugging via MCP (Model Context Protocol).
-
-- **Next.js 16+**: Enabled by default, use `next-devtools-mcp`
-- **Next.js < 16**: Requires `experimental.mcpServer: true` in next.config.js
-
-Reference: https://nextjs.org/docs/app/guides/mcp
-
-**Important**: Find the actual port of the running Next.js dev server (check terminal output or `package.json` scripts). Don't assume port 3000.
-
-### Request Format
-
-The endpoint uses JSON-RPC 2.0 over HTTP POST:
-
-```bash
-curl -X POST http://localhost:<port>/_next/mcp \
-  -H "Content-Type: application/json" \
-  -H "Accept: application/json, text/event-stream" \
-  -d '{
-    "jsonrpc": "2.0",
-    "id": "1",
-    "method": "tools/call",
-    "params": {
-      "name": "<tool-name>",
-      "arguments": {}
-    }
-  }'
-```
-
-### Available Tools
-
-#### `get_errors`
-Get current errors from dev server (build errors, runtime errors with source-mapped stacks):
-```json
-{ "name": "get_errors", "arguments": {} }
-```
-
-#### `get_routes`
-Discover all routes by scanning filesystem:
-```json
-{ "name": "get_routes", "arguments": {} }
-// Optional: { "name": "get_routes", "arguments": { "routerType": "app" } }
-```
-Returns: `{ "appRouter": ["/", "/api/users/[id]", ...], "pagesRouter": [...] }`
-
-#### `get_project_metadata`
-Get project path and dev server URL:
-```json
-{ "name": "get_project_metadata", "arguments": {} }
-```
-Returns: `{ "projectPath": "/path/to/project", "devServerUrl": "http://localhost:3000" }`
-
-#### `get_page_metadata`
-Get runtime metadata about current page render (requires active browser session):
-```json
-{ "name": "get_page_metadata", "arguments": {} }
-```
-Returns segment trie data showing layouts, boundaries, and page components.
-
-#### `get_logs`
-Get path to Next.js development log file:
-```json
-{ "name": "get_logs", "arguments": {} }
-```
-Returns path to `<distDir>/logs/next-development.log`
-
-#### `get_server_action_by_id`
-Locate a Server Action by ID:
-```json
-{ "name": "get_server_action_by_id", "arguments": { "actionId": "<action-id>" } }
-```
-
-### Example: Get Errors
-
-```bash
-curl -X POST http://localhost:<port>/_next/mcp \
-  -H "Content-Type: application/json" \
-  -H "Accept: application/json, text/event-stream" \
-  -d '{"jsonrpc":"2.0","id":"1","method":"tools/call","params":{"name":"get_errors","arguments":{}}}'
-```
-
-## Rebuild Specific Routes (Next.js 16+)
-
-Use `--debug-build-paths` to rebuild only specific routes instead of the entire app:
-
-```bash
-# Rebuild a specific route
-next build --debug-build-paths "/dashboard"
-
-# Rebuild routes matching a glob
-next build --debug-build-paths "/api/*"
-
-# Dynamic routes
-next build --debug-build-paths "/blog/[slug]"
-```
-
-Use this to:
-- Quickly verify a build fix without full rebuild
-- Debug static generation issues for specific pages
-- Iterate faster on build errors

+ 0 - 73
neo/.agents/skills/next-best-practices/directives.md

@@ -1,73 +0,0 @@
-# Directives
-
-## React Directives
-
-These are React directives, not Next.js specific.
-
-### `'use client'`
-
-Marks a component as a Client Component. Required for:
-- React hooks (`useState`, `useEffect`, etc.)
-- Event handlers (`onClick`, `onChange`)
-- Browser APIs (`window`, `localStorage`)
-
-```tsx
-'use client'
-
-import { useState } from 'react'
-
-export function Counter() {
-  const [count, setCount] = useState(0)
-  return <button onClick={() => setCount(count + 1)}>{count}</button>
-}
-```
-
-Reference: https://react.dev/reference/rsc/use-client
-
-### `'use server'`
-
-Marks a function as a Server Action. Can be passed to Client Components.
-
-```tsx
-'use server'
-
-export async function submitForm(formData: FormData) {
-  // Runs on server
-}
-```
-
-Or inline within a Server Component:
-
-```tsx
-export default function Page() {
-  async function submit() {
-    'use server'
-    // Runs on server
-  }
-  return <form action={submit}>...</form>
-}
-```
-
-Reference: https://react.dev/reference/rsc/use-server
-
----
-
-## Next.js Directive
-
-### `'use cache'`
-
-Marks a function or component for caching. Part of Next.js Cache Components.
-
-```tsx
-'use cache'
-
-export async function getCachedData() {
-  return await fetchData()
-}
-```
-
-Requires `cacheComponents: true` in `next.config.ts`.
-
-For detailed usage including cache profiles, `cacheLife()`, `cacheTag()`, and `updateTag()`, see the `next-cache-components` skill.
-
-Reference: https://nextjs.org/docs/app/api-reference/directives/use-cache

+ 0 - 227
neo/.agents/skills/next-best-practices/error-handling.md

@@ -1,227 +0,0 @@
-# Error Handling
-
-Handle errors gracefully in Next.js applications.
-
-Reference: https://nextjs.org/docs/app/getting-started/error-handling
-
-## Error Boundaries
-
-### `error.tsx`
-
-Catches errors in a route segment and its children:
-
-```tsx
-'use client'
-
-export default function Error({
-  error,
-  reset,
-}: {
-  error: Error & { digest?: string }
-  reset: () => void
-}) {
-  return (
-    <div>
-      <h2>Something went wrong!</h2>
-      <button onClick={() => reset()}>Try again</button>
-    </div>
-  )
-}
-```
-
-**Important:** `error.tsx` must be a Client Component.
-
-### `global-error.tsx`
-
-Catches errors in root layout:
-
-```tsx
-'use client'
-
-export default function GlobalError({
-  error,
-  reset,
-}: {
-  error: Error & { digest?: string }
-  reset: () => void
-}) {
-  return (
-    <html>
-      <body>
-        <h2>Something went wrong!</h2>
-        <button onClick={() => reset()}>Try again</button>
-      </body>
-    </html>
-  )
-}
-```
-
-**Important:** Must include `<html>` and `<body>` tags.
-
-## Server Actions: Navigation API Gotcha
-
-**Do NOT wrap navigation APIs in try-catch.** They throw special errors that Next.js handles internally.
-
-Reference: https://nextjs.org/docs/app/api-reference/functions/redirect#behavior
-
-```tsx
-'use server'
-
-import { redirect } from 'next/navigation'
-import { notFound } from 'next/navigation'
-
-// Bad: try-catch catches the navigation "error"
-async function createPost(formData: FormData) {
-  try {
-    const post = await db.post.create({ ... })
-    redirect(`/posts/${post.id}`)  // This throws!
-  } catch (error) {
-    // redirect() throw is caught here - navigation fails!
-    return { error: 'Failed to create post' }
-  }
-}
-
-// Good: Call navigation APIs outside try-catch
-async function createPost(formData: FormData) {
-  let post
-  try {
-    post = await db.post.create({ ... })
-  } catch (error) {
-    return { error: 'Failed to create post' }
-  }
-  redirect(`/posts/${post.id}`)  // Outside try-catch
-}
-
-// Good: Re-throw navigation errors
-async function createPost(formData: FormData) {
-  try {
-    const post = await db.post.create({ ... })
-    redirect(`/posts/${post.id}`)
-  } catch (error) {
-    if (error instanceof Error && error.message === 'NEXT_REDIRECT') {
-      throw error  // Re-throw navigation errors
-    }
-    return { error: 'Failed to create post' }
-  }
-}
-```
-
-Same applies to:
-- `redirect()` - 307 temporary redirect
-- `permanentRedirect()` - 308 permanent redirect
-- `notFound()` - 404 not found
-- `forbidden()` - 403 forbidden
-- `unauthorized()` - 401 unauthorized
-
-Use `unstable_rethrow()` to re-throw these errors in catch blocks:
-
-```tsx
-import { unstable_rethrow } from 'next/navigation'
-
-async function action() {
-  try {
-    // ...
-    redirect('/success')
-  } catch (error) {
-    unstable_rethrow(error) // Re-throws Next.js internal errors
-    return { error: 'Something went wrong' }
-  }
-}
-```
-
-## Redirects
-
-```tsx
-import { redirect, permanentRedirect } from 'next/navigation'
-
-// 307 Temporary - use for most cases
-redirect('/new-path')
-
-// 308 Permanent - use for URL migrations (cached by browsers)
-permanentRedirect('/new-url')
-```
-
-## Auth Errors
-
-Trigger auth-related error pages:
-
-```tsx
-import { forbidden, unauthorized } from 'next/navigation'
-
-async function Page() {
-  const session = await getSession()
-
-  if (!session) {
-    unauthorized() // Renders unauthorized.tsx (401)
-  }
-
-  if (!session.hasAccess) {
-    forbidden() // Renders forbidden.tsx (403)
-  }
-
-  return <Dashboard />
-}
-```
-
-Create corresponding error pages:
-
-```tsx
-// app/forbidden.tsx
-export default function Forbidden() {
-  return <div>You don't have access to this resource</div>
-}
-
-// app/unauthorized.tsx
-export default function Unauthorized() {
-  return <div>Please log in to continue</div>
-}
-```
-
-## Not Found
-
-### `not-found.tsx`
-
-Custom 404 page for a route segment:
-
-```tsx
-export default function NotFound() {
-  return (
-    <div>
-      <h2>Not Found</h2>
-      <p>Could not find the requested resource</p>
-    </div>
-  )
-}
-```
-
-### Triggering Not Found
-
-```tsx
-import { notFound } from 'next/navigation'
-
-export default async function Page({ params }: { params: Promise<{ id: string }> }) {
-  const { id } = await params
-  const post = await getPost(id)
-
-  if (!post) {
-    notFound()  // Renders closest not-found.tsx
-  }
-
-  return <div>{post.title}</div>
-}
-```
-
-## Error Hierarchy
-
-Errors bubble up to the nearest error boundary:
-
-```
-app/
-├── error.tsx           # Catches errors from all children
-├── blog/
-│   ├── error.tsx       # Catches errors in /blog/*
-│   └── [slug]/
-│       ├── error.tsx   # Catches errors in /blog/[slug]
-│       └── page.tsx
-└── layout.tsx          # Errors here go to global-error.tsx
-```

+ 0 - 140
neo/.agents/skills/next-best-practices/file-conventions.md

@@ -1,140 +0,0 @@
-# File Conventions
-
-Next.js App Router uses file-based routing with special file conventions.
-
-## Project Structure
-
-Reference: https://nextjs.org/docs/app/getting-started/project-structure
-
-```
-app/
-├── layout.tsx          # Root layout (required)
-├── page.tsx            # Home page (/)
-├── loading.tsx         # Loading UI
-├── error.tsx           # Error UI
-├── not-found.tsx       # 404 UI
-├── global-error.tsx    # Global error UI
-├── route.ts            # API endpoint
-├── template.tsx        # Re-rendered layout
-├── default.tsx         # Parallel route fallback
-├── blog/
-│   ├── page.tsx        # /blog
-│   └── [slug]/
-│       └── page.tsx    # /blog/:slug
-└── (group)/            # Route group (no URL impact)
-    └── page.tsx
-```
-
-## Special Files
-
-| File | Purpose |
-|------|---------|
-| `page.tsx` | UI for a route segment |
-| `layout.tsx` | Shared UI for segment and children |
-| `loading.tsx` | Loading UI (Suspense boundary) |
-| `error.tsx` | Error UI (Error boundary) |
-| `not-found.tsx` | 404 UI |
-| `route.ts` | API endpoint |
-| `template.tsx` | Like layout but re-renders on navigation |
-| `default.tsx` | Fallback for parallel routes |
-
-## Route Segments
-
-```
-app/
-├── blog/               # Static segment: /blog
-├── [slug]/             # Dynamic segment: /:slug
-├── [...slug]/          # Catch-all: /a/b/c
-├── [[...slug]]/        # Optional catch-all: / or /a/b/c
-└── (marketing)/        # Route group (ignored in URL)
-```
-
-## Parallel Routes
-
-```
-app/
-├── @analytics/
-│   └── page.tsx
-├── @sidebar/
-│   └── page.tsx
-└── layout.tsx          # Receives { analytics, sidebar } as props
-```
-
-## Intercepting Routes
-
-```
-app/
-├── feed/
-│   └── page.tsx
-├── @modal/
-│   └── (.)photo/[id]/  # Intercepts /photo/[id] from /feed
-│       └── page.tsx
-└── photo/[id]/
-    └── page.tsx
-```
-
-Conventions:
-- `(.)` - same level
-- `(..)` - one level up
-- `(..)(..)` - two levels up
-- `(...)` - from root
-
-## Private Folders
-
-```
-app/
-├── _components/        # Private folder (not a route)
-│   └── Button.tsx
-└── page.tsx
-```
-
-Prefix with `_` to exclude from routing.
-
-## Middleware / Proxy
-
-### Next.js 14-15: `middleware.ts`
-
-```ts
-// middleware.ts (root of project)
-import { NextResponse } from 'next/server';
-import type { NextRequest } from 'next/server';
-
-export function middleware(request: NextRequest) {
-  // Auth, redirects, rewrites, etc.
-  return NextResponse.next();
-}
-
-export const config = {
-  matcher: ['/dashboard/:path*', '/api/:path*'],
-};
-```
-
-### Next.js 16+: `proxy.ts`
-
-Renamed for clarity - same capabilities, different names:
-
-```ts
-// proxy.ts (root of project)
-import { NextResponse } from 'next/server';
-import type { NextRequest } from 'next/server';
-
-export function proxy(request: NextRequest) {
-  // Same logic as middleware
-  return NextResponse.next();
-}
-
-export const proxyConfig = {
-  matcher: ['/dashboard/:path*', '/api/:path*'],
-};
-```
-
-| Version | File | Export | Config |
-|---------|------|--------|--------|
-| v14-15 | `middleware.ts` | `middleware()` | `config` |
-| v16+ | `proxy.ts` | `proxy()` | `proxyConfig` |
-
-**Migration**: Run `npx @next/codemod@latest upgrade` to auto-rename.
-
-## File Conventions Reference
-
-Reference: https://nextjs.org/docs/app/api-reference/file-conventions

+ 0 - 245
neo/.agents/skills/next-best-practices/font.md

@@ -1,245 +0,0 @@
-# Font Optimization
-
-Use `next/font` for automatic font optimization with zero layout shift.
-
-## Google Fonts
-
-```tsx
-// app/layout.tsx
-import { Inter } from 'next/font/google'
-
-const inter = Inter({ subsets: ['latin'] })
-
-export default function RootLayout({ children }: { children: React.ReactNode }) {
-  return (
-    <html lang="en" className={inter.className}>
-      <body>{children}</body>
-    </html>
-  )
-}
-```
-
-## Multiple Fonts
-
-```tsx
-import { Inter, Roboto_Mono } from 'next/font/google'
-
-const inter = Inter({
-  subsets: ['latin'],
-  variable: '--font-inter',
-})
-
-const robotoMono = Roboto_Mono({
-  subsets: ['latin'],
-  variable: '--font-roboto-mono',
-})
-
-export default function RootLayout({ children }: { children: React.ReactNode }) {
-  return (
-    <html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
-      <body>{children}</body>
-    </html>
-  )
-}
-```
-
-Use in CSS:
-```css
-body {
-  font-family: var(--font-inter);
-}
-
-code {
-  font-family: var(--font-roboto-mono);
-}
-```
-
-## Font Weights and Styles
-
-```tsx
-// Single weight
-const inter = Inter({
-  subsets: ['latin'],
-  weight: '400',
-})
-
-// Multiple weights
-const inter = Inter({
-  subsets: ['latin'],
-  weight: ['400', '500', '700'],
-})
-
-// Variable font (recommended) - includes all weights
-const inter = Inter({
-  subsets: ['latin'],
-  // No weight needed - variable fonts support all weights
-})
-
-// With italic
-const inter = Inter({
-  subsets: ['latin'],
-  style: ['normal', 'italic'],
-})
-```
-
-## Local Fonts
-
-```tsx
-import localFont from 'next/font/local'
-
-const myFont = localFont({
-  src: './fonts/MyFont.woff2',
-})
-
-// Multiple files for different weights
-const myFont = localFont({
-  src: [
-    {
-      path: './fonts/MyFont-Regular.woff2',
-      weight: '400',
-      style: 'normal',
-    },
-    {
-      path: './fonts/MyFont-Bold.woff2',
-      weight: '700',
-      style: 'normal',
-    },
-  ],
-})
-
-// Variable font
-const myFont = localFont({
-  src: './fonts/MyFont-Variable.woff2',
-  variable: '--font-my-font',
-})
-```
-
-## Tailwind CSS Integration
-
-```tsx
-// app/layout.tsx
-import { Inter } from 'next/font/google'
-
-const inter = Inter({
-  subsets: ['latin'],
-  variable: '--font-inter',
-})
-
-export default function RootLayout({ children }) {
-  return (
-    <html lang="en" className={inter.variable}>
-      <body>{children}</body>
-    </html>
-  )
-}
-```
-
-```js
-// tailwind.config.js
-module.exports = {
-  theme: {
-    extend: {
-      fontFamily: {
-        sans: ['var(--font-inter)'],
-      },
-    },
-  },
-}
-```
-
-## Preloading Subsets
-
-Only load needed character subsets:
-
-```tsx
-// Latin only (most common)
-const inter = Inter({ subsets: ['latin'] })
-
-// Multiple subsets
-const inter = Inter({ subsets: ['latin', 'latin-ext', 'cyrillic'] })
-```
-
-## Display Strategy
-
-Control font loading behavior:
-
-```tsx
-const inter = Inter({
-  subsets: ['latin'],
-  display: 'swap', // Default - shows fallback, swaps when loaded
-})
-
-// Options:
-// 'auto' - browser decides
-// 'block' - short block period, then swap
-// 'swap' - immediate fallback, swap when ready (recommended)
-// 'fallback' - short block, short swap, then fallback
-// 'optional' - short block, no swap (use if font is optional)
-```
-
-## Don't Use Manual Font Links
-
-Always use `next/font` instead of `<link>` tags for Google Fonts.
-
-```tsx
-// Bad: Manual link tag (blocks rendering, no optimization)
-<link href="https://fonts.googleapis.com/css2?family=Inter" rel="stylesheet" />
-
-// Bad: Missing display and preconnect
-<link href="https://fonts.googleapis.com/css2?family=Inter" rel="stylesheet" />
-
-// Good: Use next/font (self-hosted, zero layout shift)
-import { Inter } from 'next/font/google'
-
-const inter = Inter({ subsets: ['latin'] })
-```
-
-## Common Mistakes
-
-```tsx
-// Bad: Importing font in every component
-// components/Button.tsx
-import { Inter } from 'next/font/google'
-const inter = Inter({ subsets: ['latin'] }) // Creates new instance each time!
-
-// Good: Import once in layout, use CSS variable
-// app/layout.tsx
-const inter = Inter({ subsets: ['latin'], variable: '--font-inter' })
-
-// Bad: Using @import in CSS (blocks rendering)
-/* globals.css */
-@import url('https://fonts.googleapis.com/css2?family=Inter');
-
-// Good: Use next/font (self-hosted, no network request)
-import { Inter } from 'next/font/google'
-
-// Bad: Loading all weights when only using a few
-const inter = Inter({ subsets: ['latin'] }) // Loads all weights
-
-// Good: Specify only needed weights (for non-variable fonts)
-const inter = Inter({ subsets: ['latin'], weight: ['400', '700'] })
-
-// Bad: Missing subset - loads all characters
-const inter = Inter({})
-
-// Good: Always specify subset
-const inter = Inter({ subsets: ['latin'] })
-```
-
-## Font in Specific Components
-
-```tsx
-// For component-specific fonts, export from a shared file
-// lib/fonts.ts
-import { Inter, Playfair_Display } from 'next/font/google'
-
-export const inter = Inter({ subsets: ['latin'], variable: '--font-inter' })
-export const playfair = Playfair_Display({ subsets: ['latin'], variable: '--font-playfair' })
-
-// components/Heading.tsx
-import { playfair } from '@/lib/fonts'
-
-export function Heading({ children }) {
-  return <h1 className={playfair.className}>{children}</h1>
-}
-```

+ 0 - 108
neo/.agents/skills/next-best-practices/functions.md

@@ -1,108 +0,0 @@
-# Functions
-
-Next.js function APIs.
-
-Reference: https://nextjs.org/docs/app/api-reference/functions
-
-## Navigation Hooks (Client)
-
-| Hook | Purpose | Reference |
-|------|---------|-----------|
-| `useRouter` | Programmatic navigation (`push`, `replace`, `back`, `refresh`) | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-router) |
-| `usePathname` | Get current pathname | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-pathname) |
-| `useSearchParams` | Read URL search parameters | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-search-params) |
-| `useParams` | Access dynamic route parameters | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-params) |
-| `useSelectedLayoutSegment` | Active child segment (one level) | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-selected-layout-segment) |
-| `useSelectedLayoutSegments` | All active segments below layout | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-selected-layout-segments) |
-| `useLinkStatus` | Check link prefetch status | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-link-status) |
-| `useReportWebVitals` | Report Core Web Vitals metrics | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-report-web-vitals) |
-
-## Server Functions
-
-| Function | Purpose | Reference |
-|----------|---------|-----------|
-| `cookies` | Read/write cookies | [Docs](https://nextjs.org/docs/app/api-reference/functions/cookies) |
-| `headers` | Read request headers | [Docs](https://nextjs.org/docs/app/api-reference/functions/headers) |
-| `draftMode` | Enable preview of unpublished CMS content | [Docs](https://nextjs.org/docs/app/api-reference/functions/draft-mode) |
-| `after` | Run code after response finishes streaming | [Docs](https://nextjs.org/docs/app/api-reference/functions/after) |
-| `connection` | Wait for connection before dynamic rendering | [Docs](https://nextjs.org/docs/app/api-reference/functions/connection) |
-| `userAgent` | Parse User-Agent header | [Docs](https://nextjs.org/docs/app/api-reference/functions/userAgent) |
-
-## Generate Functions
-
-| Function | Purpose | Reference |
-|----------|---------|-----------|
-| `generateStaticParams` | Pre-render dynamic routes at build time | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-static-params) |
-| `generateMetadata` | Dynamic metadata | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-metadata) |
-| `generateViewport` | Dynamic viewport config | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-viewport) |
-| `generateSitemaps` | Multiple sitemaps for large sites | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-sitemaps) |
-| `generateImageMetadata` | Multiple OG images per route | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-image-metadata) |
-
-## Request/Response
-
-| Function | Purpose | Reference |
-|----------|---------|-----------|
-| `NextRequest` | Extended Request with helpers | [Docs](https://nextjs.org/docs/app/api-reference/functions/next-request) |
-| `NextResponse` | Extended Response with helpers | [Docs](https://nextjs.org/docs/app/api-reference/functions/next-response) |
-| `ImageResponse` | Generate OG images | [Docs](https://nextjs.org/docs/app/api-reference/functions/image-response) |
-
-## Common Examples
-
-### Navigation
-
-Use `next/link` for internal navigation instead of `<a>` tags.
-
-```tsx
-// Bad: Plain anchor tag
-<a href="/about">About</a>
-
-// Good: Next.js Link
-import Link from 'next/link'
-
-<Link href="/about">About</Link>
-```
-
-Active link styling:
-
-```tsx
-'use client'
-
-import Link from 'next/link'
-import { usePathname } from 'next/navigation'
-
-export function NavLink({ href, children }) {
-  const pathname = usePathname()
-
-  return (
-    <Link href={href} className={pathname === href ? 'active' : ''}>
-      {children}
-    </Link>
-  )
-}
-```
-
-### Static Generation
-
-```tsx
-// app/blog/[slug]/page.tsx
-export async function generateStaticParams() {
-  const posts = await getPosts()
-  return posts.map((post) => ({ slug: post.slug }))
-}
-```
-
-### After Response
-
-```tsx
-import { after } from 'next/server'
-
-export async function POST(request: Request) {
-  const data = await processRequest(request)
-
-  after(async () => {
-    await logAnalytics(data)
-  })
-
-  return Response.json({ success: true })
-}
-```

+ 0 - 91
neo/.agents/skills/next-best-practices/hydration-error.md

@@ -1,91 +0,0 @@
-# Hydration Errors
-
-Diagnose and fix React hydration mismatch errors.
-
-## Error Signs
-
-- "Hydration failed because the initial UI does not match"
-- "Text content does not match server-rendered HTML"
-
-## Debugging
-
-In development, click the hydration error to see the server/client diff.
-
-## Common Causes and Fixes
-
-### Browser-only APIs
-
-```tsx
-// Bad: Causes mismatch - window doesn't exist on server
-<div>{window.innerWidth}</div>
-
-// Good: Use client component with mounted check
-'use client'
-import { useState, useEffect } from 'react'
-
-export function ClientOnly({ children }: { children: React.ReactNode }) {
-  const [mounted, setMounted] = useState(false)
-  useEffect(() => setMounted(true), [])
-  return mounted ? children : null
-}
-```
-
-### Date/Time Rendering
-
-Server and client may be in different timezones:
-
-```tsx
-// Bad: Causes mismatch
-<span>{new Date().toLocaleString()}</span>
-
-// Good: Render on client only
-'use client'
-const [time, setTime] = useState<string>()
-useEffect(() => setTime(new Date().toLocaleString()), [])
-```
-
-### Random Values or IDs
-
-```tsx
-// Bad: Random values differ between server and client
-<div id={Math.random().toString()}>
-
-// Good: Use useId hook
-import { useId } from 'react'
-
-function Input() {
-  const id = useId()
-  return <input id={id} />
-}
-```
-
-### Invalid HTML Nesting
-
-```tsx
-// Bad: Invalid - div inside p
-<p><div>Content</div></p>
-
-// Bad: Invalid - p inside p
-<p><p>Nested</p></p>
-
-// Good: Valid nesting
-<div><p>Content</p></div>
-```
-
-### Third-party Scripts
-
-Scripts that modify DOM during hydration.
-
-```tsx
-// Good: Use next/script with afterInteractive
-import Script from 'next/script'
-
-export default function Page() {
-  return (
-    <Script
-      src="https://example.com/script.js"
-      strategy="afterInteractive"
-    />
-  )
-}
-```

+ 0 - 173
neo/.agents/skills/next-best-practices/image.md

@@ -1,173 +0,0 @@
-# Image Optimization
-
-Use `next/image` for automatic image optimization.
-
-## Always Use next/image
-
-```tsx
-// Bad: Avoid native img
-<img src="/hero.png" alt="Hero" />
-
-// Good: Use next/image
-import Image from 'next/image'
-<Image src="/hero.png" alt="Hero" width={800} height={400} />
-```
-
-## Required Props
-
-Images need explicit dimensions to prevent layout shift:
-
-```tsx
-// Local images - dimensions inferred automatically
-import heroImage from './hero.png'
-<Image src={heroImage} alt="Hero" />
-
-// Remote images - must specify width/height
-<Image src="https://example.com/image.jpg" alt="Hero" width={800} height={400} />
-
-// Or use fill for parent-relative sizing
-<div style={{ position: 'relative', width: '100%', height: 400 }}>
-  <Image src="/hero.png" alt="Hero" fill style={{ objectFit: 'cover' }} />
-</div>
-```
-
-## Remote Images Configuration
-
-Remote domains must be configured in `next.config.js`:
-
-```js
-// next.config.js
-module.exports = {
-  images: {
-    remotePatterns: [
-      {
-        protocol: 'https',
-        hostname: 'example.com',
-        pathname: '/images/**',
-      },
-      {
-        protocol: 'https',
-        hostname: '*.cdn.com', // Wildcard subdomain
-      },
-    ],
-  },
-}
-```
-
-## Responsive Images
-
-Use `sizes` to tell the browser which size to download:
-
-```tsx
-// Full-width hero
-<Image
-  src="/hero.png"
-  alt="Hero"
-  fill
-  sizes="100vw"
-/>
-
-// Responsive grid (3 columns on desktop, 1 on mobile)
-<Image
-  src="/card.png"
-  alt="Card"
-  fill
-  sizes="(max-width: 768px) 100vw, 33vw"
-/>
-
-// Fixed sidebar image
-<Image
-  src="/avatar.png"
-  alt="Avatar"
-  width={200}
-  height={200}
-  sizes="200px"
-/>
-```
-
-## Blur Placeholder
-
-Prevent layout shift with placeholders:
-
-```tsx
-// Local images - automatic blur hash
-import heroImage from './hero.png'
-<Image src={heroImage} alt="Hero" placeholder="blur" />
-
-// Remote images - provide blurDataURL
-<Image
-  src="https://example.com/image.jpg"
-  alt="Hero"
-  width={800}
-  height={400}
-  placeholder="blur"
-  blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..."
-/>
-
-// Or use color placeholder
-<Image
-  src="https://example.com/image.jpg"
-  alt="Hero"
-  width={800}
-  height={400}
-  placeholder="empty"
-  style={{ backgroundColor: '#e0e0e0' }}
-/>
-```
-
-## Priority Loading
-
-Use `priority` for above-the-fold images (LCP):
-
-```tsx
-// Hero image - loads immediately
-<Image src="/hero.png" alt="Hero" fill priority />
-
-// Below-fold images - lazy loaded by default (no priority needed)
-<Image src="/card.png" alt="Card" width={400} height={300} />
-```
-
-## Common Mistakes
-
-```tsx
-// Bad: Missing sizes with fill - downloads largest image
-<Image src="/hero.png" alt="Hero" fill />
-
-// Good: Add sizes for proper responsive behavior
-<Image src="/hero.png" alt="Hero" fill sizes="100vw" />
-
-// Bad: Using width/height for aspect ratio only
-<Image src="/hero.png" alt="Hero" width={16} height={9} />
-
-// Good: Use actual display dimensions or fill with sizes
-<Image src="/hero.png" alt="Hero" fill sizes="100vw" style={{ objectFit: 'cover' }} />
-
-// Bad: Remote image without config
-<Image src="https://untrusted.com/image.jpg" alt="Image" width={400} height={300} />
-// Error: Invalid src prop, hostname not configured
-
-// Good: Add hostname to next.config.js remotePatterns
-```
-
-## Static Export
-
-When using `output: 'export'`, use `unoptimized` or custom loader:
-
-```tsx
-// Option 1: Disable optimization
-<Image src="/hero.png" alt="Hero" width={800} height={400} unoptimized />
-
-// Option 2: Global config
-// next.config.js
-module.exports = {
-  output: 'export',
-  images: { unoptimized: true },
-}
-
-// Option 3: Custom loader (Cloudinary, Imgix, etc.)
-const cloudinaryLoader = ({ src, width, quality }) => {
-  return `https://res.cloudinary.com/demo/image/upload/w_${width},q_${quality || 75}/${src}`
-}
-
-<Image loader={cloudinaryLoader} src="sample.jpg" alt="Sample" width={800} height={400} />
-```

+ 0 - 301
neo/.agents/skills/next-best-practices/metadata.md

@@ -1,301 +0,0 @@
-# Metadata
-
-Add SEO metadata to Next.js pages using the Metadata API.
-
-## Important: Server Components Only
-
-The `metadata` object and `generateMetadata` function are **only supported in Server Components**. They cannot be used in Client Components.
-
-If the target page has `'use client'`:
-1. Remove `'use client'` if possible, move client logic to child components
-2. Or extract metadata to a parent Server Component layout
-3. Or split the file: Server Component with metadata imports Client Components
-
-## Static Metadata
-
-```tsx
-import type { Metadata } from 'next'
-
-export const metadata: Metadata = {
-  title: 'Page Title',
-  description: 'Page description for search engines',
-}
-```
-
-## Dynamic Metadata
-
-```tsx
-import type { Metadata } from 'next'
-
-type Props = { params: Promise<{ slug: string }> }
-
-export async function generateMetadata({ params }: Props): Promise<Metadata> {
-  const { slug } = await params
-  const post = await getPost(slug)
-  return { title: post.title, description: post.description }
-}
-```
-
-## Avoid Duplicate Fetches
-
-Use React `cache()` when the same data is needed for both metadata and page:
-
-```tsx
-import { cache } from 'react'
-
-export const getPost = cache(async (slug: string) => {
-  return await db.posts.findFirst({ where: { slug } })
-})
-```
-
-## Viewport
-
-Separate from metadata for streaming support:
-
-```tsx
-import type { Viewport } from 'next'
-
-export const viewport: Viewport = {
-  width: 'device-width',
-  initialScale: 1,
-  themeColor: '#000000',
-}
-
-// Or dynamic
-export function generateViewport({ params }): Viewport {
-  return { themeColor: getThemeColor(params) }
-}
-```
-
-## Title Templates
-
-In root layout for consistent naming:
-
-```tsx
-export const metadata: Metadata = {
-  title: { default: 'Site Name', template: '%s | Site Name' },
-}
-```
-
-## Metadata File Conventions
-
-Reference: https://nextjs.org/docs/app/getting-started/project-structure#metadata-file-conventions
-
-Place these files in `app/` directory (or route segments):
-
-| File | Purpose |
-|------|---------|
-| `favicon.ico` | Favicon |
-| `icon.png` / `icon.svg` | App icon |
-| `apple-icon.png` | Apple app icon |
-| `opengraph-image.png` | OG image |
-| `twitter-image.png` | Twitter card image |
-| `sitemap.ts` / `sitemap.xml` | Sitemap (use `generateSitemaps` for multiple) |
-| `robots.ts` / `robots.txt` | Robots directives |
-| `manifest.ts` / `manifest.json` | Web app manifest |
-
-## SEO Best Practice: Static Files Are Often Enough
-
-For most sites, **static metadata files provide excellent SEO coverage**:
-
-```
-app/
-├── favicon.ico
-├── opengraph-image.png     # Works for both OG and Twitter
-├── sitemap.ts
-├── robots.ts
-└── layout.tsx              # With title/description metadata
-```
-
-**Tips:**
-- A single `opengraph-image.png` covers both Open Graph and Twitter (Twitter falls back to OG)
-- Static `title` and `description` in layout metadata is sufficient for most pages
-- Only use dynamic `generateMetadata` when content varies per page
-
----
-
-# OG Image Generation
-
-Generate dynamic Open Graph images using `next/og`.
-
-## Important Rules
-
-1. **Use `next/og`** - not `@vercel/og` (it's built into Next.js)
-2. **No searchParams** - OG images can't access search params, use route params instead
-3. **Avoid Edge runtime** - Use default Node.js runtime
-
-```tsx
-// Good
-import { ImageResponse } from 'next/og'
-
-// Bad
-// import { ImageResponse } from '@vercel/og'
-// export const runtime = 'edge'
-```
-
-## Basic OG Image
-
-```tsx
-// app/opengraph-image.tsx
-import { ImageResponse } from 'next/og'
-
-export const alt = 'Site Name'
-export const size = { width: 1200, height: 630 }
-export const contentType = 'image/png'
-
-export default function Image() {
-  return new ImageResponse(
-    (
-      <div
-        style={{
-          fontSize: 128,
-          background: 'white',
-          width: '100%',
-          height: '100%',
-          display: 'flex',
-          alignItems: 'center',
-          justifyContent: 'center',
-        }}
-      >
-        Hello World
-      </div>
-    ),
-    { ...size }
-  )
-}
-```
-
-## Dynamic OG Image
-
-```tsx
-// app/blog/[slug]/opengraph-image.tsx
-import { ImageResponse } from 'next/og'
-
-export const alt = 'Blog Post'
-export const size = { width: 1200, height: 630 }
-export const contentType = 'image/png'
-
-type Props = { params: Promise<{ slug: string }> }
-
-export default async function Image({ params }: Props) {
-  const { slug } = await params
-  const post = await getPost(slug)
-
-  return new ImageResponse(
-    (
-      <div
-        style={{
-          fontSize: 48,
-          background: 'linear-gradient(to bottom, #1a1a1a, #333)',
-          color: 'white',
-          width: '100%',
-          height: '100%',
-          display: 'flex',
-          flexDirection: 'column',
-          alignItems: 'center',
-          justifyContent: 'center',
-          padding: 48,
-        }}
-      >
-        <div style={{ fontSize: 64, fontWeight: 'bold' }}>{post.title}</div>
-        <div style={{ marginTop: 24, opacity: 0.8 }}>{post.description}</div>
-      </div>
-    ),
-    { ...size }
-  )
-}
-```
-
-## Custom Fonts
-
-```tsx
-import { ImageResponse } from 'next/og'
-import { join } from 'path'
-import { readFile } from 'fs/promises'
-
-export default async function Image() {
-  const fontPath = join(process.cwd(), 'assets/fonts/Inter-Bold.ttf')
-  const fontData = await readFile(fontPath)
-
-  return new ImageResponse(
-    (
-      <div style={{ fontFamily: 'Inter', fontSize: 64 }}>
-        Custom Font Text
-      </div>
-    ),
-    {
-      width: 1200,
-      height: 630,
-      fonts: [{ name: 'Inter', data: fontData, style: 'normal' }],
-    }
-  )
-}
-```
-
-## File Naming
-
-- `opengraph-image.tsx` - Open Graph (Facebook, LinkedIn)
-- `twitter-image.tsx` - Twitter/X cards (optional, falls back to OG)
-
-## Styling Notes
-
-ImageResponse uses Flexbox layout:
-- Use `display: 'flex'`
-- No CSS Grid support
-- Styles must be inline objects
-
-## Multiple OG Images
-
-Use `generateImageMetadata` for multiple images per route:
-
-```tsx
-// app/blog/[slug]/opengraph-image.tsx
-import { ImageResponse } from 'next/og'
-
-export async function generateImageMetadata({ params }) {
-  const images = await getPostImages(params.slug)
-  return images.map((img, idx) => ({
-    id: idx,
-    alt: img.alt,
-    size: { width: 1200, height: 630 },
-    contentType: 'image/png',
-  }))
-}
-
-export default async function Image({ params, id }) {
-  const images = await getPostImages(params.slug)
-  const image = images[id]
-  return new ImageResponse(/* ... */)
-}
-```
-
-## Multiple Sitemaps
-
-Use `generateSitemaps` for large sites:
-
-```tsx
-// app/sitemap.ts
-import type { MetadataRoute } from 'next'
-
-export async function generateSitemaps() {
-  // Return array of sitemap IDs
-  return [{ id: 0 }, { id: 1 }, { id: 2 }]
-}
-
-export default async function sitemap({
-  id,
-}: {
-  id: number
-}): Promise<MetadataRoute.Sitemap> {
-  const start = id * 50000
-  const end = start + 50000
-  const products = await getProducts(start, end)
-
-  return products.map((product) => ({
-    url: `https://example.com/product/${product.id}`,
-    lastModified: product.updatedAt,
-  }))
-}
-```
-
-Generates `/sitemap/0.xml`, `/sitemap/1.xml`, etc.

+ 0 - 287
neo/.agents/skills/next-best-practices/parallel-routes.md

@@ -1,287 +0,0 @@
-# Parallel & Intercepting Routes
-
-Parallel routes render multiple pages in the same layout. Intercepting routes show a different UI when navigating from within your app vs direct URL access. Together they enable modal patterns.
-
-## File Structure
-
-```
-app/
-├── @modal/                    # Parallel route slot
-│   ├── default.tsx            # Required! Returns null
-│   ├── (.)photos/             # Intercepts /photos/*
-│   │   └── [id]/
-│   │       └── page.tsx       # Modal content
-│   └── [...]catchall/         # Optional: catch unmatched
-│       └── page.tsx
-├── photos/
-│   └── [id]/
-│       └── page.tsx           # Full page (direct access)
-├── layout.tsx                 # Renders both children and @modal
-└── page.tsx
-```
-
-## Step 1: Root Layout with Slot
-
-```tsx
-// app/layout.tsx
-export default function RootLayout({
-  children,
-  modal,
-}: {
-  children: React.ReactNode;
-  modal: React.ReactNode;
-}) {
-  return (
-    <html>
-      <body>
-        {children}
-        {modal}
-      </body>
-    </html>
-  );
-}
-```
-
-## Step 2: Default File (Critical!)
-
-**Every parallel route slot MUST have a `default.tsx`** to prevent 404s on hard navigation.
-
-```tsx
-// app/@modal/default.tsx
-export default function Default() {
-  return null;
-}
-```
-
-Without this file, refreshing any page will 404 because Next.js can't determine what to render in the `@modal` slot.
-
-## Step 3: Intercepting Route (Modal)
-
-The `(.)` prefix intercepts routes at the same level.
-
-```tsx
-// app/@modal/(.)photos/[id]/page.tsx
-import { Modal } from '@/components/modal';
-
-export default async function PhotoModal({
-  params
-}: {
-  params: Promise<{ id: string }>
-}) {
-  const { id } = await params;
-  const photo = await getPhoto(id);
-
-  return (
-    <Modal>
-      <img src={photo.url} alt={photo.title} />
-    </Modal>
-  );
-}
-```
-
-## Step 4: Full Page (Direct Access)
-
-```tsx
-// app/photos/[id]/page.tsx
-export default async function PhotoPage({
-  params
-}: {
-  params: Promise<{ id: string }>
-}) {
-  const { id } = await params;
-  const photo = await getPhoto(id);
-
-  return (
-    <div className="full-page">
-      <img src={photo.url} alt={photo.title} />
-      <h1>{photo.title}</h1>
-    </div>
-  );
-}
-```
-
-## Step 5: Modal Component with Correct Closing
-
-**Critical: Use `router.back()` to close modals, NOT `router.push()` or `<Link>`.**
-
-```tsx
-// components/modal.tsx
-'use client';
-
-import { useRouter } from 'next/navigation';
-import { useCallback, useEffect, useRef } from 'react';
-
-export function Modal({ children }: { children: React.ReactNode }) {
-  const router = useRouter();
-  const overlayRef = useRef<HTMLDivElement>(null);
-
-  // Close on escape key
-  useEffect(() => {
-    function onKeyDown(e: KeyboardEvent) {
-      if (e.key === 'Escape') {
-        router.back(); // Correct
-      }
-    }
-    document.addEventListener('keydown', onKeyDown);
-    return () => document.removeEventListener('keydown', onKeyDown);
-  }, [router]);
-
-  // Close on overlay click
-  const handleOverlayClick = useCallback((e: React.MouseEvent) => {
-    if (e.target === overlayRef.current) {
-      router.back(); // Correct
-    }
-  }, [router]);
-
-  return (
-    <div
-      ref={overlayRef}
-      onClick={handleOverlayClick}
-      className="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
-    >
-      <div className="bg-white rounded-lg p-6 max-w-2xl w-full mx-4">
-        <button
-          onClick={() => router.back()} // Correct!
-          className="absolute top-4 right-4"
-        >
-          Close
-        </button>
-        {children}
-      </div>
-    </div>
-  );
-}
-```
-
-### Why NOT `router.push('/')` or `<Link href="/">`?
-
-Using `push` or `Link` to "close" a modal:
-1. Adds a new history entry (back button shows modal again)
-2. Doesn't properly clear the intercepted route
-3. Can cause the modal to flash or persist unexpectedly
-
-`router.back()` correctly:
-1. Removes the intercepted route from history
-2. Returns to the previous page
-3. Properly unmounts the modal
-
-## Route Matcher Reference
-
-Matchers match **route segments**, not filesystem paths:
-
-| Matcher | Matches | Example |
-|---------|---------|---------|
-| `(.)` | Same level | `@modal/(.)photos` intercepts `/photos` |
-| `(..)` | One level up | `@modal/(..)settings` from `/dashboard/@modal` intercepts `/settings` |
-| `(..)(..)` | Two levels up | Rarely used |
-| `(...)` | From root | `@modal/(...)photos` intercepts `/photos` from anywhere |
-
-**Common mistake**: Thinking `(..)` means "parent folder" - it means "parent route segment".
-
-## Handling Hard Navigation
-
-When users directly visit `/photos/123` (bookmark, refresh, shared link):
-- The intercepting route is bypassed
-- The full `photos/[id]/page.tsx` renders
-- Modal doesn't appear (expected behavior)
-
-If you want the modal to appear on direct access too, you need additional logic:
-
-```tsx
-// app/photos/[id]/page.tsx
-import { Modal } from '@/components/modal';
-
-export default async function PhotoPage({ params }) {
-  const { id } = await params;
-  const photo = await getPhoto(id);
-
-  // Option: Render as modal on direct access too
-  return (
-    <Modal>
-      <img src={photo.url} alt={photo.title} />
-    </Modal>
-  );
-}
-```
-
-## Common Gotchas
-
-### 1. Missing `default.tsx` → 404 on Refresh
-
-Every `@slot` folder needs a `default.tsx` that returns `null` (or appropriate content).
-
-### 2. Modal Persists After Navigation
-
-You're using `router.push()` instead of `router.back()`.
-
-### 3. Nested Parallel Routes Need Defaults Too
-
-If you have `@modal` inside a route group, each level needs its own `default.tsx`:
-
-```
-app/
-├── (marketing)/
-│   ├── @modal/
-│   │   └── default.tsx     # Needed!
-│   └── layout.tsx
-└── layout.tsx
-```
-
-### 4. Intercepted Route Shows Wrong Content
-
-Check your matcher:
-- `(.)photos` intercepts `/photos` from the same route level
-- If your `@modal` is in `app/dashboard/@modal`, use `(.)photos` to intercept `/dashboard/photos`, not `/photos`
-
-### 5. TypeScript Errors with `params`
-
-In Next.js 15+, `params` is a Promise:
-
-```tsx
-// Correct
-export default async function Page({ params }: { params: Promise<{ id: string }> }) {
-  const { id } = await params;
-}
-```
-
-## Complete Example: Photo Gallery Modal
-
-```
-app/
-├── @modal/
-│   ├── default.tsx
-│   └── (.)photos/
-│       └── [id]/
-│           └── page.tsx
-├── photos/
-│   ├── page.tsx           # Gallery grid
-│   └── [id]/
-│       └── page.tsx       # Full photo page
-├── layout.tsx
-└── page.tsx
-```
-
-Links in the gallery:
-
-```tsx
-// app/photos/page.tsx
-import Link from 'next/link';
-
-export default async function Gallery() {
-  const photos = await getPhotos();
-
-  return (
-    <div className="grid grid-cols-3 gap-4">
-      {photos.map(photo => (
-        <Link key={photo.id} href={`/photos/${photo.id}`}>
-          <img src={photo.thumbnail} alt={photo.title} />
-        </Link>
-      ))}
-    </div>
-  );
-}
-```
-
-Clicking a photo → Modal opens (intercepted)
-Direct URL → Full page renders
-Refresh while modal open → Full page renders

+ 0 - 146
neo/.agents/skills/next-best-practices/route-handlers.md

@@ -1,146 +0,0 @@
-# Route Handlers
-
-Create API endpoints with `route.ts` files.
-
-## Basic Usage
-
-```tsx
-// app/api/users/route.ts
-export async function GET() {
-  const users = await getUsers()
-  return Response.json(users)
-}
-
-export async function POST(request: Request) {
-  const body = await request.json()
-  const user = await createUser(body)
-  return Response.json(user, { status: 201 })
-}
-```
-
-## Supported Methods
-
-`GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`
-
-## GET Handler Conflicts with page.tsx
-
-**A `route.ts` and `page.tsx` cannot coexist in the same folder.**
-
-```
-app/
-├── api/
-│   └── users/
-│       └── route.ts    # /api/users
-└── users/
-    ├── page.tsx        # /users (page)
-    └── route.ts        # Warning: Conflicts with page.tsx!
-```
-
-If you need both a page and an API at the same path, use different paths:
-
-```
-app/
-├── users/
-│   └── page.tsx        # /users (page)
-└── api/
-    └── users/
-        └── route.ts    # /api/users (API)
-```
-
-## Environment Behavior
-
-Route handlers run in a **Server Component-like environment**:
-
-- Yes: Can use `async/await`
-- Yes: Can access `cookies()`, `headers()`
-- Yes: Can use Node.js APIs
-- No: Cannot use React hooks
-- No: Cannot use React DOM APIs
-- No: Cannot use browser APIs
-
-```tsx
-// Bad: This won't work - no React DOM in route handlers
-import { renderToString } from 'react-dom/server'
-
-export async function GET() {
-  const html = renderToString(<Component />)  // Error!
-  return new Response(html)
-}
-```
-
-## Dynamic Route Handlers
-
-```tsx
-// app/api/users/[id]/route.ts
-export async function GET(
-  request: Request,
-  { params }: { params: Promise<{ id: string }> }
-) {
-  const { id } = await params
-  const user = await getUser(id)
-
-  if (!user) {
-    return Response.json({ error: 'Not found' }, { status: 404 })
-  }
-
-  return Response.json(user)
-}
-```
-
-## Request Helpers
-
-```tsx
-export async function GET(request: Request) {
-  // URL and search params
-  const { searchParams } = new URL(request.url)
-  const query = searchParams.get('q')
-
-  // Headers
-  const authHeader = request.headers.get('authorization')
-
-  // Cookies (Next.js helper)
-  const cookieStore = await cookies()
-  const token = cookieStore.get('token')
-
-  return Response.json({ query, token })
-}
-```
-
-## Response Helpers
-
-```tsx
-// JSON response
-return Response.json({ data })
-
-// With status
-return Response.json({ error: 'Not found' }, { status: 404 })
-
-// With headers
-return Response.json(data, {
-  headers: {
-    'Cache-Control': 'max-age=3600',
-  },
-})
-
-// Redirect
-return Response.redirect(new URL('/login', request.url))
-
-// Stream
-return new Response(stream, {
-  headers: { 'Content-Type': 'text/event-stream' },
-})
-```
-
-## When to Use Route Handlers vs Server Actions
-
-| Use Case | Route Handlers | Server Actions |
-|----------|----------------|----------------|
-| Form submissions | No | Yes |
-| Data mutations from UI | No | Yes |
-| Third-party webhooks | Yes | No |
-| External API consumption | Yes | No |
-| Public REST API | Yes | No |
-| File uploads | Both work | Both work |
-
-**Prefer Server Actions** for mutations triggered from your UI.
-**Use Route Handlers** for external integrations and public APIs.

+ 0 - 159
neo/.agents/skills/next-best-practices/rsc-boundaries.md

@@ -1,159 +0,0 @@
-# RSC Boundaries
-
-Detect and prevent invalid patterns when crossing Server/Client component boundaries.
-
-## Detection Rules
-
-### 1. Async Client Components Are Invalid
-
-Client components **cannot** be async functions. Only Server Components can be async.
-
-**Detect:** File has `'use client'` AND component is `async function` or returns `Promise`
-
-```tsx
-// Bad: async client component
-'use client'
-export default async function UserProfile() {
-  const user = await getUser() // Cannot await in client component
-  return <div>{user.name}</div>
-}
-
-// Good: Remove async, fetch data in parent server component
-// page.tsx (server component - no 'use client')
-export default async function Page() {
-  const user = await getUser()
-  return <UserProfile user={user} />
-}
-
-// UserProfile.tsx (client component)
-'use client'
-export function UserProfile({ user }: { user: User }) {
-  return <div>{user.name}</div>
-}
-```
-
-```tsx
-// Bad: async arrow function client component
-'use client'
-const Dashboard = async () => {
-  const data = await fetchDashboard()
-  return <div>{data}</div>
-}
-
-// Good: Fetch in server component, pass data down
-```
-
-### 2. Non-Serializable Props to Client Components
-
-Props passed from Server → Client must be JSON-serializable.
-
-**Detect:** Server component passes these to a client component:
-- Functions (except Server Actions with `'use server'`)
-- `Date` objects
-- `Map`, `Set`, `WeakMap`, `WeakSet`
-- Class instances
-- `Symbol` (unless globally registered)
-- Circular references
-
-```tsx
-// Bad: Function prop
-// page.tsx (server)
-export default function Page() {
-  const handleClick = () => console.log('clicked')
-  return <ClientButton onClick={handleClick} />
-}
-
-// Good: Define function inside client component
-// ClientButton.tsx
-'use client'
-export function ClientButton() {
-  const handleClick = () => console.log('clicked')
-  return <button onClick={handleClick}>Click</button>
-}
-```
-
-```tsx
-// Bad: Date object (silently becomes string, then crashes)
-// page.tsx (server)
-export default async function Page() {
-  const post = await getPost()
-  return <PostCard createdAt={post.createdAt} /> // Date object
-}
-
-// PostCard.tsx (client) - will crash on .getFullYear()
-'use client'
-export function PostCard({ createdAt }: { createdAt: Date }) {
-  return <span>{createdAt.getFullYear()}</span> // Runtime error!
-}
-
-// Good: Serialize to string on server
-// page.tsx (server)
-export default async function Page() {
-  const post = await getPost()
-  return <PostCard createdAt={post.createdAt.toISOString()} />
-}
-
-// PostCard.tsx (client)
-'use client'
-export function PostCard({ createdAt }: { createdAt: string }) {
-  const date = new Date(createdAt)
-  return <span>{date.getFullYear()}</span>
-}
-```
-
-```tsx
-// Bad: Class instance
-const user = new UserModel(data)
-<ClientProfile user={user} /> // Methods will be stripped
-
-// Good: Pass plain object
-const user = await getUser()
-<ClientProfile user={{ id: user.id, name: user.name }} />
-```
-
-```tsx
-// Bad: Map/Set
-<ClientComponent items={new Map([['a', 1]])} />
-
-// Good: Convert to array/object
-<ClientComponent items={Object.fromEntries(map)} />
-<ClientComponent items={Array.from(set)} />
-```
-
-### 3. Server Actions Are the Exception
-
-Functions marked with `'use server'` CAN be passed to client components.
-
-```tsx
-// Valid: Server Action can be passed
-// actions.ts
-'use server'
-export async function submitForm(formData: FormData) {
-  // server-side logic
-}
-
-// page.tsx (server)
-import { submitForm } from './actions'
-export default function Page() {
-  return <ClientForm onSubmit={submitForm} /> // OK!
-}
-
-// ClientForm.tsx (client)
-'use client'
-export function ClientForm({ onSubmit }: { onSubmit: (data: FormData) => Promise<void> }) {
-  return <form action={onSubmit}>...</form>
-}
-```
-
-## Quick Reference
-
-| Pattern | Valid? | Fix |
-|---------|--------|-----|
-| `'use client'` + `async function` | No | Fetch in server parent, pass data |
-| Pass `() => {}` to client | No | Define in client or use server action |
-| Pass `new Date()` to client | No | Use `.toISOString()` |
-| Pass `new Map()` to client | No | Convert to object/array |
-| Pass class instance to client | No | Pass plain object |
-| Pass server action to client | Yes | - |
-| Pass `string/number/boolean` | Yes | - |
-| Pass plain object/array | Yes | - |

+ 0 - 39
neo/.agents/skills/next-best-practices/runtime-selection.md

@@ -1,39 +0,0 @@
-# Runtime Selection
-
-## Use Node.js Runtime by Default
-
-Use the default Node.js runtime for new routes and pages. Only use Edge runtime if the project already uses it or there's a specific requirement.
-
-```tsx
-// Good: Default - no runtime config needed (uses Node.js)
-export default function Page() { ... }
-
-// Caution: Only if already used in project or specifically required
-export const runtime = 'edge'
-```
-
-## When to Use Each
-
-### Node.js Runtime (Default)
-
-- Full Node.js API support
-- File system access (`fs`)
-- Full `crypto` support
-- Database connections
-- Most npm packages work
-
-### Edge Runtime
-
-- Only for specific edge-location latency requirements
-- Limited API (no `fs`, limited `crypto`)
-- Smaller cold start
-- Geographic distribution needs
-
-## Detection
-
-**Before adding `runtime = 'edge'`**, check:
-1. Does the project already use Edge runtime?
-2. Is there a specific latency requirement?
-3. Are all dependencies Edge-compatible?
-
-If unsure, use Node.js runtime.

+ 0 - 141
neo/.agents/skills/next-best-practices/scripts.md

@@ -1,141 +0,0 @@
-# Scripts
-
-Loading third-party scripts in Next.js.
-
-## Use next/script
-
-Always use `next/script` instead of native `<script>` tags for better performance.
-
-```tsx
-// Bad: Native script tag
-<script src="https://example.com/script.js"></script>
-
-// Good: Next.js Script component
-import Script from 'next/script'
-
-<Script src="https://example.com/script.js" />
-```
-
-## Inline Scripts Need ID
-
-Inline scripts require an `id` attribute for Next.js to track them.
-
-```tsx
-// Bad: Missing id
-<Script dangerouslySetInnerHTML={{ __html: 'console.log("hi")' }} />
-
-// Good: Has id
-<Script id="my-script" dangerouslySetInnerHTML={{ __html: 'console.log("hi")' }} />
-
-// Good: Inline with id
-<Script id="show-banner">
-  {`document.getElementById('banner').classList.remove('hidden')`}
-</Script>
-```
-
-## Don't Put Script in Head
-
-`next/script` should not be placed inside `next/head`. It handles its own positioning.
-
-```tsx
-// Bad: Script inside Head
-import Head from 'next/head'
-import Script from 'next/script'
-
-<Head>
-  <Script src="/analytics.js" />
-</Head>
-
-// Good: Script outside Head
-<Head>
-  <title>Page</title>
-</Head>
-<Script src="/analytics.js" />
-```
-
-## Loading Strategies
-
-```tsx
-// afterInteractive (default) - Load after page is interactive
-<Script src="/analytics.js" strategy="afterInteractive" />
-
-// lazyOnload - Load during idle time
-<Script src="/widget.js" strategy="lazyOnload" />
-
-// beforeInteractive - Load before page is interactive (use sparingly)
-// Only works in app/layout.tsx or pages/_document.js
-<Script src="/critical.js" strategy="beforeInteractive" />
-
-// worker - Load in web worker (experimental)
-<Script src="/heavy.js" strategy="worker" />
-```
-
-## Google Analytics
-
-Use `@next/third-parties` instead of inline GA scripts.
-
-```tsx
-// Bad: Inline GA script
-<Script src="https://www.googletagmanager.com/gtag/js?id=G-XXXXX" />
-<Script id="ga-init">
-  {`window.dataLayer = window.dataLayer || [];
-    function gtag(){dataLayer.push(arguments);}
-    gtag('js', new Date());
-    gtag('config', 'G-XXXXX');`}
-</Script>
-
-// Good: Next.js component
-import { GoogleAnalytics } from '@next/third-parties/google'
-
-export default function Layout({ children }) {
-  return (
-    <html>
-      <body>{children}</body>
-      <GoogleAnalytics gaId="G-XXXXX" />
-    </html>
-  )
-}
-```
-
-## Google Tag Manager
-
-```tsx
-import { GoogleTagManager } from '@next/third-parties/google'
-
-export default function Layout({ children }) {
-  return (
-    <html>
-      <GoogleTagManager gtmId="GTM-XXXXX" />
-      <body>{children}</body>
-    </html>
-  )
-}
-```
-
-## Other Third-Party Scripts
-
-```tsx
-// YouTube embed
-import { YouTubeEmbed } from '@next/third-parties/google'
-
-<YouTubeEmbed videoid="dQw4w9WgXcQ" />
-
-// Google Maps
-import { GoogleMapsEmbed } from '@next/third-parties/google'
-
-<GoogleMapsEmbed
-  apiKey="YOUR_API_KEY"
-  mode="place"
-  q="Brooklyn+Bridge,New+York,NY"
-/>
-```
-
-## Quick Reference
-
-| Pattern | Issue | Fix |
-|---------|-------|-----|
-| `<script src="...">` | No optimization | Use `next/script` |
-| `<Script>` without id | Can't track inline scripts | Add `id` attribute |
-| `<Script>` inside `<Head>` | Wrong placement | Move outside Head |
-| Inline GA/GTM scripts | No optimization | Use `@next/third-parties` |
-| `strategy="beforeInteractive"` outside layout | Won't work | Only use in root layout |

+ 0 - 371
neo/.agents/skills/next-best-practices/self-hosting.md

@@ -1,371 +0,0 @@
-# Self-Hosting Next.js
-
-Deploy Next.js outside of Vercel with confidence.
-
-## Quick Start: Standalone Output
-
-For Docker or any containerized deployment, use standalone output:
-
-```js
-// next.config.js
-module.exports = {
-  output: 'standalone',
-};
-```
-
-This creates a minimal `standalone` folder with only production dependencies:
-
-```
-.next/
-├── standalone/
-│   ├── server.js          # Entry point
-│   ├── node_modules/      # Only production deps
-│   └── .next/             # Build output
-└── static/                # Must be copied separately
-```
-
-## Docker Deployment
-
-### Dockerfile
-
-```dockerfile
-FROM node:20-alpine AS base
-
-# Install dependencies
-FROM base AS deps
-WORKDIR /app
-COPY package.json package-lock.json* ./
-RUN npm ci
-
-# Build
-FROM base AS builder
-WORKDIR /app
-COPY --from=deps /app/node_modules ./node_modules
-COPY . .
-RUN npm run build
-
-# Production
-FROM base AS runner
-WORKDIR /app
-
-ENV NODE_ENV=production
-
-# Create non-root user
-RUN addgroup --system --gid 1001 nodejs
-RUN adduser --system --uid 1001 nextjs
-
-# Copy standalone output
-COPY --from=builder /app/.next/standalone ./
-COPY --from=builder /app/.next/static ./.next/static
-COPY --from=builder /app/public ./public
-
-USER nextjs
-
-EXPOSE 3000
-ENV PORT=3000
-ENV HOSTNAME="0.0.0.0"
-
-CMD ["node", "server.js"]
-```
-
-### Docker Compose
-
-```yaml
-version: '3.8'
-
-services:
-  web:
-    build: .
-    ports:
-      - "3000:3000"
-    environment:
-      - NODE_ENV=production
-    restart: unless-stopped
-    healthcheck:
-      test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/api/health"]
-      interval: 30s
-      timeout: 10s
-      retries: 3
-```
-
-## PM2 Deployment
-
-For traditional server deployments:
-
-```js
-// ecosystem.config.js
-module.exports = {
-  apps: [{
-    name: 'nextjs',
-    script: '.next/standalone/server.js',
-    instances: 'max',
-    exec_mode: 'cluster',
-    env: {
-      NODE_ENV: 'production',
-      PORT: 3000,
-    },
-  }],
-};
-```
-
-```bash
-npm run build
-pm2 start ecosystem.config.js
-```
-
-## ISR and Cache Handlers
-
-### The Problem
-
-ISR (Incremental Static Regeneration) uses filesystem caching by default. This **breaks with multiple instances**:
-
-- Instance A regenerates page → saves to its local disk
-- Instance B serves stale page → doesn't see Instance A's cache
-- Load balancer sends users to random instances → inconsistent content
-
-### Solution: Custom Cache Handler
-
-Next.js 14+ supports custom cache handlers for shared storage:
-
-```js
-// next.config.js
-module.exports = {
-  cacheHandler: require.resolve('./cache-handler.js'),
-  cacheMaxMemorySize: 0, // Disable in-memory cache
-};
-```
-
-#### Redis Cache Handler Example
-
-```js
-// cache-handler.js
-const Redis = require('ioredis');
-
-const redis = new Redis(process.env.REDIS_URL);
-const CACHE_PREFIX = 'nextjs:';
-
-module.exports = class CacheHandler {
-  constructor(options) {
-    this.options = options;
-  }
-
-  async get(key) {
-    const data = await redis.get(CACHE_PREFIX + key);
-    if (!data) return null;
-
-    const parsed = JSON.parse(data);
-    return {
-      value: parsed.value,
-      lastModified: parsed.lastModified,
-    };
-  }
-
-  async set(key, data, ctx) {
-    const cacheData = {
-      value: data,
-      lastModified: Date.now(),
-    };
-
-    // Set TTL based on revalidate option
-    if (ctx?.revalidate) {
-      await redis.setex(
-        CACHE_PREFIX + key,
-        ctx.revalidate,
-        JSON.stringify(cacheData)
-      );
-    } else {
-      await redis.set(CACHE_PREFIX + key, JSON.stringify(cacheData));
-    }
-  }
-
-  async revalidateTag(tags) {
-    // Implement tag-based invalidation
-    // This requires tracking which keys have which tags
-  }
-};
-```
-
-#### S3 Cache Handler Example
-
-```js
-// cache-handler.js
-const { S3Client, GetObjectCommand, PutObjectCommand } = require('@aws-sdk/client-s3');
-
-const s3 = new S3Client({ region: process.env.AWS_REGION });
-const BUCKET = process.env.CACHE_BUCKET;
-
-module.exports = class CacheHandler {
-  async get(key) {
-    try {
-      const response = await s3.send(new GetObjectCommand({
-        Bucket: BUCKET,
-        Key: `cache/${key}`,
-      }));
-      const body = await response.Body.transformToString();
-      return JSON.parse(body);
-    } catch (err) {
-      if (err.name === 'NoSuchKey') return null;
-      throw err;
-    }
-  }
-
-  async set(key, data, ctx) {
-    await s3.send(new PutObjectCommand({
-      Bucket: BUCKET,
-      Key: `cache/${key}`,
-      Body: JSON.stringify({
-        value: data,
-        lastModified: Date.now(),
-      }),
-      ContentType: 'application/json',
-    }));
-  }
-};
-```
-
-## What Works vs What Needs Setup
-
-| Feature | Single Instance | Multi-Instance | Notes |
-|---------|----------------|----------------|-------|
-| SSR | Yes | Yes | No special setup |
-| SSG | Yes | Yes | Built at deploy time |
-| ISR | Yes | Needs cache handler | Filesystem cache breaks |
-| Image Optimization | Yes | Yes | CPU-intensive, consider CDN |
-| Middleware | Yes | Yes | Runs on Node.js |
-| Edge Runtime | Limited | Limited | Some features Node-only |
-| `revalidatePath/Tag` | Yes | Needs cache handler | Must share cache |
-| `next/font` | Yes | Yes | Fonts bundled at build |
-| Draft Mode | Yes | Yes | Cookie-based |
-
-## Image Optimization
-
-Next.js Image Optimization works out of the box but is CPU-intensive.
-
-### Option 1: Built-in (Simple)
-
-Works automatically, but consider:
-- Set `deviceSizes` and `imageSizes` in config to limit variants
-- Use `minimumCacheTTL` to reduce regeneration
-
-```js
-// next.config.js
-module.exports = {
-  images: {
-    minimumCacheTTL: 60 * 60 * 24, // 24 hours
-    deviceSizes: [640, 750, 1080, 1920], // Limit sizes
-  },
-};
-```
-
-### Option 2: External Loader (Recommended for Scale)
-
-Offload to Cloudinary, Imgix, or similar:
-
-```js
-// next.config.js
-module.exports = {
-  images: {
-    loader: 'custom',
-    loaderFile: './lib/image-loader.js',
-  },
-};
-```
-
-```js
-// lib/image-loader.js
-export default function cloudinaryLoader({ src, width, quality }) {
-  const params = ['f_auto', 'c_limit', `w_${width}`, `q_${quality || 'auto'}`];
-  return `https://res.cloudinary.com/demo/image/upload/${params.join(',')}${src}`;
-}
-```
-
-## Environment Variables
-
-### Build-time vs Runtime
-
-```js
-// Available at build time only (baked into bundle)
-NEXT_PUBLIC_API_URL=https://api.example.com
-
-// Available at runtime (server-side only)
-DATABASE_URL=postgresql://...
-API_SECRET=...
-```
-
-### Runtime Configuration
-
-For truly dynamic config, don't use `NEXT_PUBLIC_*`. Instead:
-
-```tsx
-// app/api/config/route.ts
-export async function GET() {
-  return Response.json({
-    apiUrl: process.env.API_URL,
-    features: process.env.FEATURES?.split(','),
-  });
-}
-```
-
-## OpenNext: Serverless Without Vercel
-
-[OpenNext](https://open-next.js.org/) adapts Next.js for AWS Lambda, Cloudflare Workers, etc.
-
-```bash
-npx create-sst@latest
-# or
-npx @opennextjs/aws build
-```
-
-Supports:
-- AWS Lambda + CloudFront
-- Cloudflare Workers
-- Netlify Functions
-- Deno Deploy
-
-## Health Check Endpoint
-
-Always include a health check for load balancers:
-
-```tsx
-// app/api/health/route.ts
-export async function GET() {
-  try {
-    // Optional: check database connection
-    // await db.$queryRaw`SELECT 1`;
-
-    return Response.json({ status: 'healthy' }, { status: 200 });
-  } catch (error) {
-    return Response.json({ status: 'unhealthy' }, { status: 503 });
-  }
-}
-```
-
-## Pre-Deployment Checklist
-
-1. **Build locally first**: `npm run build` - catch errors before deploy
-2. **Test standalone output**: `node .next/standalone/server.js`
-3. **Set `output: 'standalone'`** for Docker
-4. **Configure cache handler** for multi-instance ISR
-5. **Set `HOSTNAME="0.0.0.0"`** for containers
-6. **Copy `public/` and `.next/static/`** - not included in standalone
-7. **Add health check endpoint**
-8. **Test ISR revalidation** after deployment
-9. **Monitor memory usage** - Node.js defaults may need tuning
-
-## Testing Cache Handler
-
-**Critical**: Test your cache handler on every Next.js upgrade:
-
-```bash
-# Start multiple instances
-PORT=3001 node .next/standalone/server.js &
-PORT=3002 node .next/standalone/server.js &
-
-# Trigger ISR revalidation
-curl http://localhost:3001/api/revalidate?path=/posts
-
-# Verify both instances see the update
-curl http://localhost:3001/posts
-curl http://localhost:3002/posts
-# Should return identical content
-```

+ 0 - 67
neo/.agents/skills/next-best-practices/suspense-boundaries.md

@@ -1,67 +0,0 @@
-# Suspense Boundaries
-
-Client hooks that cause CSR bailout without Suspense boundaries.
-
-## useSearchParams
-
-Always requires Suspense boundary in static routes. Without it, the entire page becomes client-side rendered.
-
-```tsx
-// Bad: Entire page becomes CSR
-'use client'
-
-import { useSearchParams } from 'next/navigation'
-
-export default function SearchBar() {
-  const searchParams = useSearchParams()
-  return <div>Query: {searchParams.get('q')}</div>
-}
-```
-
-```tsx
-// Good: Wrap in Suspense
-import { Suspense } from 'react'
-import SearchBar from './search-bar'
-
-export default function Page() {
-  return (
-    <Suspense fallback={<div>Loading...</div>}>
-      <SearchBar />
-    </Suspense>
-  )
-}
-```
-
-## usePathname
-
-Requires Suspense boundary when route has dynamic parameters.
-
-```tsx
-// In dynamic route [slug]
-// Bad: No Suspense
-'use client'
-import { usePathname } from 'next/navigation'
-
-export function Breadcrumb() {
-  const pathname = usePathname()
-  return <nav>{pathname}</nav>
-}
-```
-
-```tsx
-// Good: Wrap in Suspense
-<Suspense fallback={<BreadcrumbSkeleton />}>
-  <Breadcrumb />
-</Suspense>
-```
-
-If you use `generateStaticParams`, Suspense is optional.
-
-## Quick Reference
-
-| Hook | Suspense Required |
-|------|-------------------|
-| `useSearchParams()` | Yes |
-| `usePathname()` | Yes (dynamic routes) |
-| `useParams()` | No |
-| `useRouter()` | No |

+ 0 - 411
neo/.agents/skills/next-cache-components/SKILL.md

@@ -1,411 +0,0 @@
----
-name: next-cache-components
-description: Next.js 16 Cache Components - PPR, use cache directive, cacheLife, cacheTag, updateTag
----
-
-# Cache Components (Next.js 16+)
-
-Cache Components enable Partial Prerendering (PPR) - mix static, cached, and dynamic content in a single route.
-
-## Enable Cache Components
-
-```ts
-// next.config.ts
-import type { NextConfig } from 'next'
-
-const nextConfig: NextConfig = {
-  cacheComponents: true,
-}
-
-export default nextConfig
-```
-
-This replaces the old `experimental.ppr` flag.
-
----
-
-## Three Content Types
-
-With Cache Components enabled, content falls into three categories:
-
-### 1. Static (Auto-Prerendered)
-
-Synchronous code, imports, pure computations - prerendered at build time:
-
-```tsx
-export default function Page() {
-  return (
-    <header>
-      <h1>Our Blog</h1>  {/* Static - instant */}
-      <nav>...</nav>
-    </header>
-  )
-}
-```
-
-### 2. Cached (`use cache`)
-
-Async data that doesn't need fresh fetches every request:
-
-```tsx
-async function BlogPosts() {
-  'use cache'
-  cacheLife('hours')
-
-  const posts = await db.posts.findMany()
-  return <PostList posts={posts} />
-}
-```
-
-### 3. Dynamic (Suspense)
-
-Runtime data that must be fresh - wrap in Suspense:
-
-```tsx
-import { Suspense } from 'react'
-
-export default function Page() {
-  return (
-    <>
-      <BlogPosts />  {/* Cached */}
-
-      <Suspense fallback={<p>Loading...</p>}>
-        <UserPreferences />  {/* Dynamic - streams in */}
-      </Suspense>
-    </>
-  )
-}
-
-async function UserPreferences() {
-  const theme = (await cookies()).get('theme')?.value
-  return <p>Theme: {theme}</p>
-}
-```
-
----
-
-## `use cache` Directive
-
-### File Level
-
-```tsx
-'use cache'
-
-export default async function Page() {
-  // Entire page is cached
-  const data = await fetchData()
-  return <div>{data}</div>
-}
-```
-
-### Component Level
-
-```tsx
-export async function CachedComponent() {
-  'use cache'
-  const data = await fetchData()
-  return <div>{data}</div>
-}
-```
-
-### Function Level
-
-```tsx
-export async function getData() {
-  'use cache'
-  return db.query('SELECT * FROM posts')
-}
-```
-
----
-
-## Cache Profiles
-
-### Built-in Profiles
-
-```tsx
-'use cache'                    // Default: 5m stale, 15m revalidate
-```
-
-```tsx
-'use cache: remote'           // Platform-provided cache (Redis, KV)
-```
-
-```tsx
-'use cache: private'          // For compliance, allows runtime APIs
-```
-
-### `cacheLife()` - Custom Lifetime
-
-```tsx
-import { cacheLife } from 'next/cache'
-
-async function getData() {
-  'use cache'
-  cacheLife('hours')  // Built-in profile
-  return fetch('/api/data')
-}
-```
-
-Built-in profiles: `'default'`, `'minutes'`, `'hours'`, `'days'`, `'weeks'`, `'max'`
-
-### Inline Configuration
-
-```tsx
-async function getData() {
-  'use cache'
-  cacheLife({
-    stale: 3600,      // 1 hour - serve stale while revalidating
-    revalidate: 7200, // 2 hours - background revalidation interval
-    expire: 86400,    // 1 day - hard expiration
-  })
-  return fetch('/api/data')
-}
-```
-
----
-
-## Cache Invalidation
-
-### `cacheTag()` - Tag Cached Content
-
-```tsx
-import { cacheTag } from 'next/cache'
-
-async function getProducts() {
-  'use cache'
-  cacheTag('products')
-  return db.products.findMany()
-}
-
-async function getProduct(id: string) {
-  'use cache'
-  cacheTag('products', `product-${id}`)
-  return db.products.findUnique({ where: { id } })
-}
-```
-
-### `updateTag()` - Immediate Invalidation
-
-Use when you need the cache refreshed within the same request:
-
-```tsx
-'use server'
-
-import { updateTag } from 'next/cache'
-
-export async function updateProduct(id: string, data: FormData) {
-  await db.products.update({ where: { id }, data })
-  updateTag(`product-${id}`)  // Immediate - same request sees fresh data
-}
-```
-
-### `revalidateTag()` - Background Revalidation
-
-Use for stale-while-revalidate behavior:
-
-```tsx
-'use server'
-
-import { revalidateTag } from 'next/cache'
-
-export async function createPost(data: FormData) {
-  await db.posts.create({ data })
-  revalidateTag('posts')  // Background - next request sees fresh data
-}
-```
-
----
-
-## Runtime Data Constraint
-
-**Cannot** access `cookies()`, `headers()`, or `searchParams` inside `use cache`.
-
-### Solution: Pass as Arguments
-
-```tsx
-// Wrong - runtime API inside use cache
-async function CachedProfile() {
-  'use cache'
-  const session = (await cookies()).get('session')?.value  // Error!
-  return <div>{session}</div>
-}
-
-// Correct - extract outside, pass as argument
-async function ProfilePage() {
-  const session = (await cookies()).get('session')?.value
-  return <CachedProfile sessionId={session} />
-}
-
-async function CachedProfile({ sessionId }: { sessionId: string }) {
-  'use cache'
-  // sessionId becomes part of cache key automatically
-  const data = await fetchUserData(sessionId)
-  return <div>{data.name}</div>
-}
-```
-
-### Exception: `use cache: private`
-
-For compliance requirements when you can't refactor:
-
-```tsx
-async function getData() {
-  'use cache: private'
-  const session = (await cookies()).get('session')?.value  // Allowed
-  return fetchData(session)
-}
-```
-
----
-
-## Cache Key Generation
-
-Cache keys are automatic based on:
-- **Build ID** - invalidates all caches on deploy
-- **Function ID** - hash of function location
-- **Serializable arguments** - props become part of key
-- **Closure variables** - outer scope values included
-
-```tsx
-async function Component({ userId }: { userId: string }) {
-  const getData = async (filter: string) => {
-    'use cache'
-    // Cache key = userId (closure) + filter (argument)
-    return fetch(`/api/users/${userId}?filter=${filter}`)
-  }
-  return getData('active')
-}
-```
-
----
-
-## Complete Example
-
-```tsx
-import { Suspense } from 'react'
-import { cookies } from 'next/headers'
-import { cacheLife, cacheTag } from 'next/cache'
-
-export default function DashboardPage() {
-  return (
-    <>
-      {/* Static shell - instant from CDN */}
-      <header><h1>Dashboard</h1></header>
-      <nav>...</nav>
-
-      {/* Cached - fast, revalidates hourly */}
-      <Stats />
-
-      {/* Dynamic - streams in with fresh data */}
-      <Suspense fallback={<NotificationsSkeleton />}>
-        <Notifications />
-      </Suspense>
-    </>
-  )
-}
-
-async function Stats() {
-  'use cache'
-  cacheLife('hours')
-  cacheTag('dashboard-stats')
-
-  const stats = await db.stats.aggregate()
-  return <StatsDisplay stats={stats} />
-}
-
-async function Notifications() {
-  const userId = (await cookies()).get('userId')?.value
-  const notifications = await db.notifications.findMany({
-    where: { userId, read: false }
-  })
-  return <NotificationList items={notifications} />
-}
-```
-
----
-
-## Migration from Previous Versions
-
-| Old Config | Replacement |
-|-----------|-------------|
-| `experimental.ppr` | `cacheComponents: true` |
-| `dynamic = 'force-dynamic'` | Remove (default behavior) |
-| `dynamic = 'force-static'` | `'use cache'` + `cacheLife('max')` |
-| `revalidate = N` | `cacheLife({ revalidate: N })` |
-| `unstable_cache()` | `'use cache'` directive |
-
-### Migrating `unstable_cache` to `use cache`
-
-`unstable_cache` has been replaced by the `use cache` directive in Next.js 16. When `cacheComponents` is enabled, convert `unstable_cache` calls to `use cache` functions:
-
-**Before (`unstable_cache`):**
-
-```tsx
-import { unstable_cache } from 'next/cache'
-
-const getCachedUser = unstable_cache(
-  async (id) => getUser(id),
-  ['my-app-user'],
-  {
-    tags: ['users'],
-    revalidate: 60,
-  }
-)
-
-export default async function Page({ params }: { params: Promise<{ id: string }> }) {
-  const { id } = await params
-  const user = await getCachedUser(id)
-  return <div>{user.name}</div>
-}
-```
-
-**After (`use cache`):**
-
-```tsx
-import { cacheLife, cacheTag } from 'next/cache'
-
-async function getCachedUser(id: string) {
-  'use cache'
-  cacheTag('users')
-  cacheLife({ revalidate: 60 })
-  return getUser(id)
-}
-
-export default async function Page({ params }: { params: Promise<{ id: string }> }) {
-  const { id } = await params
-  const user = await getCachedUser(id)
-  return <div>{user.name}</div>
-}
-```
-
-Key differences:
-- **No manual cache keys** - `use cache` generates keys automatically from function arguments and closures. The `keyParts` array from `unstable_cache` is no longer needed.
-- **Tags** - Replace `options.tags` with `cacheTag()` calls inside the function.
-- **Revalidation** - Replace `options.revalidate` with `cacheLife({ revalidate: N })` or a built-in profile like `cacheLife('minutes')`.
-- **Dynamic data** - `unstable_cache` did not support `cookies()` or `headers()` inside the callback. The same restriction applies to `use cache`, but you can use `'use cache: private'` if needed.
-
----
-
-## Limitations
-
-- **Edge runtime not supported** - requires Node.js
-- **Static export not supported** - needs server
-- **Non-deterministic values** (`Math.random()`, `Date.now()`) execute once at build time inside `use cache`
-
-For request-time randomness outside cache:
-
-```tsx
-import { connection } from 'next/server'
-
-async function DynamicContent() {
-  await connection()  // Defer to request time
-  const id = crypto.randomUUID()  // Different per request
-  return <div>{id}</div>
-}
-```
-
-Sources:
-- [Cache Components Guide](https://nextjs.org/docs/app/getting-started/cache-components)
-- [use cache Directive](https://nextjs.org/docs/app/api-reference/directives/use-cache)
-- [unstable_cache (legacy)](https://nextjs.org/docs/app/api-reference/functions/unstable_cache)

+ 0 - 50
neo/.agents/skills/next-upgrade/SKILL.md

@@ -1,50 +0,0 @@
----
-name: next-upgrade
-description: Upgrade Next.js to the latest version following official migration guides and codemods
-argument-hint: "[target-version]"
----
-
-# Upgrade Next.js
-
-Upgrade the current project to the latest Next.js version following official migration guides.
-
-## Instructions
-
-1. **Detect current version**: Read `package.json` to identify the current Next.js version and related dependencies (React, React DOM, etc.)
-
-2. **Fetch the latest upgrade guide**: Use WebFetch to get the official upgrade documentation:
-   - Codemods: https://nextjs.org/docs/app/guides/upgrading/codemods
-   - Version-specific guides (adjust version as needed):
-     - https://nextjs.org/docs/app/guides/upgrading/version-16 
-     - https://nextjs.org/docs/app/guides/upgrading/version-15
-     - https://nextjs.org/docs/app/guides/upgrading/version-14
-
-3. **Determine upgrade path**: Based on current version, identify which migration steps apply. For major version jumps, upgrade incrementally (e.g., 13 → 14 → 15).
-
-4. **Run codemods first**: Next.js provides codemods to automate breaking changes:
-   ```bash
-   npx @next/codemod@latest <transform> <path>
-   ```
-   Common transforms:
-   - `next-async-request-api` - Updates async Request APIs (v15)
-   - `next-request-geo-ip` - Migrates geo/ip properties (v15)
-   - `next-dynamic-access-named-export` - Transforms dynamic imports (v15)
-
-5. **Update dependencies**: Upgrade Next.js and peer dependencies together:
-   ```bash
-   npm install next@latest react@latest react-dom@latest
-   ```
-
-6. **Review breaking changes**: Check the upgrade guide for manual changes needed:
-   - API changes (e.g., async params in v15)
-   - Configuration changes in `next.config.js`
-   - Deprecated features being removed
-
-7. **Update TypeScript types** (if applicable):
-   ```bash
-   npm install @types/react@latest @types/react-dom@latest
-   ```
-
-8. **Test the upgrade**:
-   - Run `npm run build` to check for build errors
-   - Run `npm run dev` and test key functionality

+ 0 - 32
neo/.agents/skills/react-doctor/SKILL.md

@@ -1,32 +0,0 @@
----
-name: react-doctor
-description: Use when finishing a feature, fixing a bug, before committing React code, or when the user wants to improve code quality or clean up a codebase. Checks for score regression. Covers lint, dead code, accessibility, bundle size, architecture diagnostics.
-version: "1.0.0"
----
-
-# React Doctor
-
-Scans React codebases for security, performance, correctness, and architecture issues. Outputs a 0–100 health score.
-
-## After making React code changes:
-
-Run `npx -y react-doctor@latest . --verbose --diff` and check the score did not regress.
-
-If the score dropped, fix the regressions before committing.
-
-## For general cleanup or code improvement:
-
-Run `npx -y react-doctor@latest . --verbose` (without `--diff`) to scan the full codebase. Fix issues by severity — errors first, then warnings.
-
-## Command
-
-```bash
-npx -y react-doctor@latest . --verbose --diff
-```
-
-| Flag        | Purpose                                       |
-| ----------- | --------------------------------------------- |
-| `.`         | Scan current directory                        |
-| `--verbose` | Show affected files and line numbers per rule |
-| `--diff`    | Only scan changed files vs base branch        |
-| `--score`   | Output only the numeric score                 |

+ 0 - 951
neo/.agents/skills/turborepo/SKILL.md

@@ -1,951 +0,0 @@
----
-name: turborepo
-description: |
-  Turborepo monorepo build system guidance. Triggers on: turbo.json, task pipelines,
-  dependsOn, caching, remote cache, the "turbo" CLI, --filter, --affected, CI optimization, environment
-  variables, internal packages, monorepo structure/best practices, and boundaries.
-
-  Use when user: configures tasks/workflows/pipelines, creates packages, sets up
-  monorepo, shares code between apps, runs changed/affected packages, debugs cache,
-  or has apps/packages directories.
-metadata:
-  version: 2.8.21-canary.9
----
-
-# Turborepo Skill
-
-Build system for JavaScript/TypeScript monorepos. Turborepo caches task outputs and runs tasks in parallel based on dependency graph.
-
-## IMPORTANT: Package Tasks, Not Root Tasks
-
-**DO NOT create Root Tasks. ALWAYS create package tasks.**
-
-When creating tasks/scripts/pipelines, you MUST:
-
-1. Add the script to each relevant package's `package.json`
-2. Register the task in root `turbo.json`
-3. Root `package.json` only delegates via `turbo run <task>`
-
-**DO NOT** put task logic in root `package.json`. This defeats Turborepo's parallelization.
-
-```json
-// DO THIS: Scripts in each package
-// apps/web/package.json
-{ "scripts": { "build": "next build", "lint": "eslint .", "test": "vitest" } }
-
-// apps/api/package.json
-{ "scripts": { "build": "tsc", "lint": "eslint .", "test": "vitest" } }
-
-// packages/ui/package.json
-{ "scripts": { "build": "tsc", "lint": "eslint .", "test": "vitest" } }
-```
-
-```json
-// turbo.json - register tasks
-{
-  "tasks": {
-    "build": { "dependsOn": ["^build"], "outputs": ["dist/**"] },
-    "lint": {},
-    "test": { "dependsOn": ["build"] }
-  }
-}
-```
-
-```json
-// Root package.json - ONLY delegates, no task logic
-{
-  "scripts": {
-    "build": "turbo run build",
-    "lint": "turbo run lint",
-    "test": "turbo run test"
-  }
-}
-```
-
-```json
-// DO NOT DO THIS - defeats parallelization
-// Root package.json
-{
-  "scripts": {
-    "build": "cd apps/web && next build && cd ../api && tsc",
-    "lint": "eslint apps/ packages/",
-    "test": "vitest"
-  }
-}
-```
-
-Root Tasks (`//#taskname`) are ONLY for tasks that truly cannot exist in packages (rare).
-
-## Secondary Rule: `turbo run` vs `turbo`
-
-**Always use `turbo run` when the command is written into code:**
-
-```json
-// package.json - ALWAYS "turbo run"
-{
-  "scripts": {
-    "build": "turbo run build"
-  }
-}
-```
-
-```yaml
-# CI workflows - ALWAYS "turbo run"
-- run: turbo run build --affected
-```
-
-**The shorthand `turbo <tasks>` is ONLY for one-off terminal commands** typed directly by humans or agents. Never write `turbo build` into package.json, CI, or scripts.
-
-## Quick Decision Trees
-
-### "I need to configure a task"
-
-```
-Configure a task?
-├─ Define task dependencies → references/configuration/tasks.md
-├─ Lint/check-types (parallel + caching) → Use Transit Nodes pattern (see below)
-├─ Specify build outputs → references/configuration/tasks.md#outputs
-├─ Handle environment variables → references/environment/RULE.md
-├─ Set up dev/watch tasks → references/configuration/tasks.md#persistent
-├─ Package-specific config → references/configuration/RULE.md#package-configurations
-└─ Global settings (cacheDir, daemon) → references/configuration/global-options.md
-```
-
-### "My cache isn't working"
-
-```
-Cache problems?
-├─ Tasks run but outputs not restored → Missing `outputs` key
-├─ Cache misses unexpectedly → references/caching/gotchas.md
-├─ Need to debug hash inputs → Use --summarize or --dry
-├─ Want to skip cache entirely → Use --force or cache: false
-├─ Remote cache not working → references/caching/remote-cache.md
-└─ Environment causing misses → references/environment/gotchas.md
-```
-
-### "I want to run only changed packages"
-
-```
-Run only what changed?
-├─ Changed packages + dependents (RECOMMENDED) → turbo run build --affected
-├─ Custom base branch → --affected --affected-base=origin/develop
-├─ Manual git comparison → --filter=...[origin/main]
-└─ See all filter options → references/filtering/RULE.md
-```
-
-**`--affected` is the primary way to run only changed packages.** It automatically compares against the default branch and includes dependents.
-
-### "I want to filter packages"
-
-```
-Filter packages?
-├─ Only changed packages → --affected (see above)
-├─ By package name → --filter=web
-├─ By directory → --filter=./apps/*
-├─ Package + dependencies → --filter=web...
-├─ Package + dependents → --filter=...web
-└─ Complex combinations → references/filtering/patterns.md
-```
-
-### "Environment variables aren't working"
-
-```
-Environment issues?
-├─ Vars not available at runtime → Strict mode filtering (default)
-├─ Cache hits with wrong env → Var not in `env` key
-├─ .env changes not causing rebuilds → .env not in `inputs`
-├─ CI variables missing → references/environment/gotchas.md
-└─ Framework vars (NEXT_PUBLIC_*) → Auto-included via inference
-```
-
-### "I need to set up CI"
-
-```
-CI setup?
-├─ GitHub Actions → references/ci/github-actions.md
-├─ Vercel deployment → references/ci/vercel.md
-├─ Remote cache in CI → references/caching/remote-cache.md
-├─ Only build changed packages → --affected flag
-├─ Skip unnecessary builds → turbo-ignore (references/cli/commands.md)
-└─ Skip container setup when no changes → turbo-ignore
-```
-
-### "I want to watch for changes during development"
-
-```
-Watch mode?
-├─ Re-run tasks on change → turbo watch (references/watch/RULE.md)
-├─ Dev servers with dependencies → Use `with` key (references/configuration/tasks.md#with)
-├─ Restart dev server on dep change → Use `interruptible: true`
-└─ Persistent dev tasks → Use `persistent: true`
-```
-
-### "I need to create/structure a package"
-
-```
-Package creation/structure?
-├─ Create an internal package → references/best-practices/packages.md
-├─ Repository structure → references/best-practices/structure.md
-├─ Dependency management → references/best-practices/dependencies.md
-├─ Best practices overview → references/best-practices/RULE.md
-├─ JIT vs Compiled packages → references/best-practices/packages.md#compilation-strategies
-└─ Sharing code between apps → references/best-practices/RULE.md#package-types
-```
-
-### "How should I structure my monorepo?"
-
-```
-Monorepo structure?
-├─ Standard layout (apps/, packages/) → references/best-practices/RULE.md
-├─ Package types (apps vs libraries) → references/best-practices/RULE.md#package-types
-├─ Creating internal packages → references/best-practices/packages.md
-├─ TypeScript configuration → references/best-practices/structure.md#typescript-configuration
-├─ ESLint configuration → references/best-practices/structure.md#eslint-configuration
-├─ Dependency management → references/best-practices/dependencies.md
-└─ Enforce package boundaries → references/boundaries/RULE.md
-```
-
-### "I want to enforce architectural boundaries"
-
-```
-Enforce boundaries?
-├─ Check for violations → turbo boundaries
-├─ Tag packages → references/boundaries/RULE.md#tags
-├─ Restrict which packages can import others → references/boundaries/RULE.md#rule-types
-└─ Prevent cross-package file imports → references/boundaries/RULE.md
-```
-
-## Critical Anti-Patterns
-
-### Using `turbo` Shorthand in Code
-
-**`turbo run` is recommended in package.json scripts and CI pipelines.** The shorthand `turbo <task>` is intended for interactive terminal use.
-
-```json
-// WRONG - using shorthand in package.json
-{
-  "scripts": {
-    "build": "turbo build",
-    "dev": "turbo dev"
-  }
-}
-
-// CORRECT
-{
-  "scripts": {
-    "build": "turbo run build",
-    "dev": "turbo run dev"
-  }
-}
-```
-
-```yaml
-# WRONG - using shorthand in CI
-- run: turbo build --affected
-
-# CORRECT
-- run: turbo run build --affected
-```
-
-### Root Scripts Bypassing Turbo
-
-Root `package.json` scripts MUST delegate to `turbo run`, not run tasks directly.
-
-```json
-// WRONG - bypasses turbo entirely
-{
-  "scripts": {
-    "build": "bun build",
-    "dev": "bun dev"
-  }
-}
-
-// CORRECT - delegates to turbo
-{
-  "scripts": {
-    "build": "turbo run build",
-    "dev": "turbo run dev"
-  }
-}
-```
-
-### Using `&&` to Chain Turbo Tasks
-
-Don't chain turbo tasks with `&&`. Let turbo orchestrate.
-
-```json
-// WRONG - turbo task not using turbo run
-{
-  "scripts": {
-    "changeset:publish": "bun build && changeset publish"
-  }
-}
-
-// CORRECT
-{
-  "scripts": {
-    "changeset:publish": "turbo run build && changeset publish"
-  }
-}
-```
-
-### `prebuild` Scripts That Manually Build Dependencies
-
-Scripts like `prebuild` that manually build other packages bypass Turborepo's dependency graph.
-
-```json
-// WRONG - manually building dependencies
-{
-  "scripts": {
-    "prebuild": "cd ../../packages/types && bun run build && cd ../utils && bun run build",
-    "build": "next build"
-  }
-}
-```
-
-**However, the fix depends on whether workspace dependencies are declared:**
-
-1. **If dependencies ARE declared** (e.g., `"@repo/types": "workspace:*"` in package.json), remove the `prebuild` script. Turbo's `dependsOn: ["^build"]` handles this automatically.
-
-2. **If dependencies are NOT declared**, the `prebuild` exists because `^build` won't trigger without a dependency relationship. The fix is to:
-   - Add the dependency to package.json: `"@repo/types": "workspace:*"`
-   - Then remove the `prebuild` script
-
-```json
-// CORRECT - declare dependency, let turbo handle build order
-// package.json
-{
-  "dependencies": {
-    "@repo/types": "workspace:*",
-    "@repo/utils": "workspace:*"
-  },
-  "scripts": {
-    "build": "next build"
-  }
-}
-
-// turbo.json
-{
-  "tasks": {
-    "build": {
-      "dependsOn": ["^build"]
-    }
-  }
-}
-```
-
-**Key insight:** `^build` only runs build in packages listed as dependencies. No dependency declaration = no automatic build ordering.
-
-### Overly Broad `globalDependencies`
-
-`globalDependencies` affects ALL tasks in ALL packages via the **global hash** — tasks cannot opt out of specific files, even with negation globs in `inputs`. Be specific.
-
-```json
-// WRONG - heavy hammer, affects all hashes
-{
-  "globalDependencies": ["**/.env.*local"]
-}
-
-// BETTER - move to task-level inputs
-{
-  "globalDependencies": [".env"],
-  "tasks": {
-    "build": {
-      "inputs": ["$TURBO_DEFAULT$", ".env*"],
-      "outputs": ["dist/**"]
-    }
-  }
-}
-```
-
-With `futureFlags.globalConfiguration`, this problem is reduced because `global.inputs` files are folded into each task's inputs (not the global hash). Tasks can exclude specific files:
-
-```json
-// BEST - global.inputs with per-task exclusion
-{
-  "futureFlags": { "globalConfiguration": true },
-  "global": {
-    "inputs": [".env"]
-  },
-  "tasks": {
-    "build": { "outputs": ["dist/**"] },
-    "lint": {
-      "inputs": ["$TURBO_DEFAULT$", "!$TURBO_ROOT$/.env"]
-    }
-  }
-}
-```
-
-### Repetitive Task Configuration
-
-Look for repeated configuration across tasks that can be collapsed. Turborepo supports shared configuration patterns.
-
-```json
-// WRONG - repetitive env and inputs across tasks
-{
-  "tasks": {
-    "build": {
-      "env": ["API_URL", "DATABASE_URL"],
-      "inputs": ["$TURBO_DEFAULT$", ".env*"]
-    },
-    "test": {
-      "env": ["API_URL", "DATABASE_URL"],
-      "inputs": ["$TURBO_DEFAULT$", ".env*"]
-    },
-    "dev": {
-      "env": ["API_URL", "DATABASE_URL"],
-      "inputs": ["$TURBO_DEFAULT$", ".env*"],
-      "cache": false,
-      "persistent": true
-    }
-  }
-}
-
-// BETTER - use globalEnv and globalDependencies for shared config
-{
-  "globalEnv": ["API_URL", "DATABASE_URL"],
-  "globalDependencies": [".env*"],
-  "tasks": {
-    "build": {},
-    "test": {},
-    "dev": {
-      "cache": false,
-      "persistent": true
-    }
-  }
-}
-```
-
-**When to use global vs task-level:**
-
-- `globalEnv` / `globalDependencies` - affects ALL tasks, use for truly shared config
-- Task-level `env` / `inputs` - use when only specific tasks need it
-
-### NOT an Anti-Pattern: Large `env` Arrays
-
-A large `env` array (even 50+ variables) is **not** a problem. It usually means the user was thorough about declaring their build's environment dependencies. Do not flag this as an issue.
-
-### Using `--parallel` Flag
-
-The `--parallel` flag bypasses Turborepo's dependency graph. If tasks need parallel execution, configure `dependsOn` correctly instead.
-
-```bash
-# WRONG - bypasses dependency graph
-turbo run lint --parallel
-
-# CORRECT - configure tasks to allow parallel execution
-# In turbo.json, set dependsOn appropriately (or use transit nodes)
-turbo run lint
-```
-
-### Package-Specific Task Overrides in Root turbo.json
-
-When multiple packages need different task configurations, use **Package Configurations** (`turbo.json` in each package) instead of cluttering root `turbo.json` with `package#task` overrides.
-
-```json
-// WRONG - root turbo.json with many package-specific overrides
-{
-  "tasks": {
-    "test": { "dependsOn": ["build"] },
-    "@repo/web#test": { "outputs": ["coverage/**"] },
-    "@repo/api#test": { "outputs": ["coverage/**"] },
-    "@repo/utils#test": { "outputs": [] },
-    "@repo/cli#test": { "outputs": [] },
-    "@repo/core#test": { "outputs": [] }
-  }
-}
-
-// CORRECT - use Package Configurations
-// Root turbo.json - base config only
-{
-  "tasks": {
-    "test": { "dependsOn": ["build"] }
-  }
-}
-
-// packages/web/turbo.json - package-specific override
-{
-  "extends": ["//"],
-  "tasks": {
-    "test": { "outputs": ["coverage/**"] }
-  }
-}
-
-// packages/api/turbo.json
-{
-  "extends": ["//"],
-  "tasks": {
-    "test": { "outputs": ["coverage/**"] }
-  }
-}
-```
-
-**Benefits of Package Configurations:**
-
-- Keeps configuration close to the code it affects
-- Root turbo.json stays clean and focused on base patterns
-- Easier to understand what's special about each package
-- Works with `$TURBO_EXTENDS$` to inherit + extend arrays
-
-**When to use `package#task` in root:**
-
-- Single package needs a unique dependency (e.g., `"deploy": { "dependsOn": ["web#build"] }`)
-- Temporary override while migrating
-
-See `references/configuration/RULE.md#package-configurations` for full details.
-
-### Using `../` to Traverse Out of Package in `inputs`
-
-Don't use relative paths like `../` to reference files outside the package. Use `$TURBO_ROOT$` instead.
-
-```json
-// WRONG - traversing out of package
-{
-  "tasks": {
-    "build": {
-      "inputs": ["$TURBO_DEFAULT$", "../shared-config.json"]
-    }
-  }
-}
-
-// CORRECT - use $TURBO_ROOT$ for repo root
-{
-  "tasks": {
-    "build": {
-      "inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/shared-config.json"]
-    }
-  }
-}
-```
-
-### Missing `outputs` for File-Producing Tasks
-
-**Before flagging missing `outputs`, check what the task actually produces:**
-
-1. Read the package's script (e.g., `"build": "tsc"`, `"test": "vitest"`)
-2. Determine if it writes files to disk or only outputs to stdout
-3. Only flag if the task produces files that should be cached
-
-```json
-// WRONG: build produces files but they're not cached
-{
-  "tasks": {
-    "build": {
-      "dependsOn": ["^build"]
-    }
-  }
-}
-
-// CORRECT: build outputs are cached
-{
-  "tasks": {
-    "build": {
-      "dependsOn": ["^build"],
-      "outputs": ["dist/**"]
-    }
-  }
-}
-```
-
-Common outputs by framework:
-
-- Next.js: `[".next/**", "!.next/cache/**"]`
-- Vite/Rollup: `["dist/**"]`
-- tsc: `["dist/**"]` or custom `outDir`
-
-**TypeScript `--noEmit` can still produce cache files:**
-
-When `incremental: true` in tsconfig.json, `tsc --noEmit` writes `.tsbuildinfo` files even without emitting JS. Check the tsconfig before assuming no outputs:
-
-```json
-// If tsconfig has incremental: true, tsc --noEmit produces cache files
-{
-  "tasks": {
-    "typecheck": {
-      "outputs": ["node_modules/.cache/tsbuildinfo.json"] // or wherever tsBuildInfoFile points
-    }
-  }
-}
-```
-
-To determine correct outputs for TypeScript tasks:
-
-1. Check if `incremental` or `composite` is enabled in tsconfig
-2. Check `tsBuildInfoFile` for custom cache location (default: alongside `outDir` or in project root)
-3. If no incremental mode, `tsc --noEmit` produces no files
-
-### `^build` vs `build` Confusion
-
-```json
-{
-  "tasks": {
-    // ^build = run build in DEPENDENCIES first (other packages this one imports)
-    "build": {
-      "dependsOn": ["^build"]
-    },
-    // build (no ^) = run build in SAME PACKAGE first
-    "test": {
-      "dependsOn": ["build"]
-    },
-    // pkg#task = specific package's task
-    "deploy": {
-      "dependsOn": ["web#build"]
-    }
-  }
-}
-```
-
-### Environment Variables Not Hashed
-
-```json
-// WRONG: API_URL changes won't cause rebuilds
-{
-  "tasks": {
-    "build": {
-      "outputs": ["dist/**"]
-    }
-  }
-}
-
-// CORRECT: API_URL changes invalidate cache
-{
-  "tasks": {
-    "build": {
-      "outputs": ["dist/**"],
-      "env": ["API_URL", "API_KEY"]
-    }
-  }
-}
-```
-
-### `.env` Files Not in Inputs
-
-Turbo does NOT load `.env` files - your framework does. But Turbo needs to know about changes:
-
-```json
-// WRONG: .env changes don't invalidate cache
-{
-  "tasks": {
-    "build": {
-      "env": ["API_URL"]
-    }
-  }
-}
-
-// CORRECT: .env file changes invalidate cache
-{
-  "tasks": {
-    "build": {
-      "env": ["API_URL"],
-      "inputs": ["$TURBO_DEFAULT$", ".env", ".env.*"]
-    }
-  }
-}
-```
-
-### Root `.env` File in Monorepo
-
-A `.env` file at the repo root is an anti-pattern — even for small monorepos or starter templates. It creates implicit coupling between packages and makes it unclear which packages depend on which variables.
-
-```
-// WRONG - root .env affects all packages implicitly
-my-monorepo/
-├── .env              # Which packages use this?
-├── apps/
-│   ├── web/
-│   └── api/
-└── packages/
-
-// CORRECT - .env files in packages that need them
-my-monorepo/
-├── apps/
-│   ├── web/
-│   │   └── .env      # Clear: web needs DATABASE_URL
-│   └── api/
-│       └── .env      # Clear: api needs API_KEY
-└── packages/
-```
-
-**Problems with root `.env`:**
-
-- Unclear which packages consume which variables
-- All packages get all variables (even ones they don't need)
-- Cache invalidation is coarse-grained (root .env change invalidates everything)
-- Security risk: packages may accidentally access sensitive vars meant for others
-- Bad habits start small — starter templates should model correct patterns
-
-**If you must share variables**, use `globalEnv` to be explicit about what's shared, and document why.
-
-### Strict Mode Filtering CI Variables
-
-By default, Turborepo filters environment variables to only those in `env`/`globalEnv`. CI variables may be missing:
-
-```json
-// If CI scripts need GITHUB_TOKEN but it's not in env:
-{
-  "globalPassThroughEnv": ["GITHUB_TOKEN", "CI"],
-  "tasks": { ... }
-}
-```
-
-Or use `--env-mode=loose` (not recommended for production).
-
-### Shared Code in Apps (Should Be a Package)
-
-```
-// WRONG: Shared code inside an app
-apps/
-  web/
-    shared/          # This breaks monorepo principles!
-      utils.ts
-
-// CORRECT: Extract to a package
-packages/
-  utils/
-    src/utils.ts
-```
-
-### Accessing Files Across Package Boundaries
-
-```typescript
-// WRONG: Reaching into another package's internals
-import { Button } from "../../packages/ui/src/button";
-
-// CORRECT: Install and import properly
-import { Button } from "@repo/ui/button";
-```
-
-### Too Many Root Dependencies
-
-```json
-// WRONG: App dependencies in root
-{
-  "dependencies": {
-    "react": "^18",
-    "next": "^14"
-  }
-}
-
-// CORRECT: Only repo tools in root
-{
-  "devDependencies": {
-    "turbo": "latest"
-  }
-}
-```
-
-## Common Task Configurations
-
-### Standard Build Pipeline
-
-```json
-{
-  "$schema": "https://v2-8-21-canary-9.turborepo.dev/schema.json",
-  "tasks": {
-    "build": {
-      "dependsOn": ["^build"],
-      "outputs": ["dist/**", ".next/**", "!.next/cache/**"]
-    },
-    "dev": {
-      "cache": false,
-      "persistent": true
-    }
-  }
-}
-```
-
-Add a `transit` task if you have tasks that need parallel execution with cache invalidation (see below).
-
-### Dev Task with `^dev` Pattern (for `turbo watch`)
-
-A `dev` task with `dependsOn: ["^dev"]` and `persistent: false` in root turbo.json may look unusual but is **correct for `turbo watch` workflows**:
-
-```json
-// Root turbo.json
-{
-  "tasks": {
-    "dev": {
-      "dependsOn": ["^dev"],
-      "cache": false,
-      "persistent": false  // Packages have one-shot dev scripts
-    }
-  }
-}
-
-// Package turbo.json (apps/web/turbo.json)
-{
-  "extends": ["//"],
-  "tasks": {
-    "dev": {
-      "persistent": true  // Apps run long-running dev servers
-    }
-  }
-}
-```
-
-**Why this works:**
-
-- **Packages** (e.g., `@acme/db`, `@acme/validators`) have `"dev": "tsc"` — one-shot type generation that completes quickly
-- **Apps** override with `persistent: true` for actual dev servers (Next.js, etc.)
-- **`turbo watch`** re-runs the one-shot package `dev` scripts when source files change, keeping types in sync
-
-**Intended usage:** Run `turbo watch dev` (not `turbo run dev`). Watch mode re-executes one-shot tasks on file changes while keeping persistent tasks running.
-
-**Alternative pattern:** Use a separate task name like `prepare` or `generate` for one-shot dependency builds to make the intent clearer:
-
-```json
-{
-  "tasks": {
-    "prepare": {
-      "dependsOn": ["^prepare"],
-      "outputs": ["dist/**"]
-    },
-    "dev": {
-      "dependsOn": ["prepare"],
-      "cache": false,
-      "persistent": true
-    }
-  }
-}
-```
-
-### Transit Nodes for Parallel Tasks with Cache Invalidation
-
-Some tasks can run in parallel (don't need built output from dependencies) but must invalidate cache when dependency source code changes.
-
-**The problem with `dependsOn: ["^taskname"]`:**
-
-- Forces sequential execution (slow)
-
-**The problem with `dependsOn: []` (no dependencies):**
-
-- Allows parallel execution (fast)
-- But cache is INCORRECT - changing dependency source won't invalidate cache
-
-**Transit Nodes solve both:**
-
-```json
-{
-  "tasks": {
-    "transit": { "dependsOn": ["^transit"] },
-    "my-task": { "dependsOn": ["transit"] }
-  }
-}
-```
-
-The `transit` task creates dependency relationships without matching any actual script, so tasks run in parallel with correct cache invalidation.
-
-**How to identify tasks that need this pattern:** Look for tasks that read source files from dependencies but don't need their build outputs.
-
-### With Environment Variables
-
-```json
-{
-  "globalEnv": ["NODE_ENV"],
-  "globalDependencies": [".env"],
-  "tasks": {
-    "build": {
-      "dependsOn": ["^build"],
-      "outputs": ["dist/**"],
-      "env": ["API_URL", "DATABASE_URL"]
-    }
-  }
-}
-```
-
-With `futureFlags.globalConfiguration`, the same config moves global settings under `global` — and `.env` becomes a per-task input instead of a global hash input:
-
-```json
-{
-  "futureFlags": { "globalConfiguration": true },
-  "global": {
-    "env": ["NODE_ENV"],
-    "inputs": [".env"]
-  },
-  "tasks": {
-    "build": {
-      "dependsOn": ["^build"],
-      "outputs": ["dist/**"],
-      "env": ["API_URL", "DATABASE_URL"]
-    }
-  }
-}
-```
-
-## Reference Index
-
-### Configuration
-
-| File                                                                            | Purpose                                                                   |
-| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
-| [configuration/RULE.md](./references/configuration/RULE.md)                     | turbo.json overview, Package Configurations                               |
-| [configuration/tasks.md](./references/configuration/tasks.md)                   | dependsOn, outputs, inputs, env, cache, persistent                        |
-| [configuration/global-options.md](./references/configuration/global-options.md) | globalEnv, globalDependencies, global key, futureFlags, cacheDir, envMode |
-| [configuration/gotchas.md](./references/configuration/gotchas.md)               | Common configuration mistakes                                             |
-
-### Caching
-
-| File                                                            | Purpose                                      |
-| --------------------------------------------------------------- | -------------------------------------------- |
-| [caching/RULE.md](./references/caching/RULE.md)                 | How caching works, hash inputs               |
-| [caching/remote-cache.md](./references/caching/remote-cache.md) | Vercel Remote Cache, self-hosted, login/link |
-| [caching/gotchas.md](./references/caching/gotchas.md)           | Debugging cache misses, --summarize, --dry   |
-
-### Environment Variables
-
-| File                                                          | Purpose                                   |
-| ------------------------------------------------------------- | ----------------------------------------- |
-| [environment/RULE.md](./references/environment/RULE.md)       | env, globalEnv, passThroughEnv            |
-| [environment/modes.md](./references/environment/modes.md)     | Strict vs Loose mode, framework inference |
-| [environment/gotchas.md](./references/environment/gotchas.md) | .env files, CI issues                     |
-
-### Filtering
-
-| File                                                        | Purpose                  |
-| ----------------------------------------------------------- | ------------------------ |
-| [filtering/RULE.md](./references/filtering/RULE.md)         | --filter syntax overview |
-| [filtering/patterns.md](./references/filtering/patterns.md) | Common filter patterns   |
-
-### CI/CD
-
-| File                                                      | Purpose                         |
-| --------------------------------------------------------- | ------------------------------- |
-| [ci/RULE.md](./references/ci/RULE.md)                     | General CI principles           |
-| [ci/github-actions.md](./references/ci/github-actions.md) | Complete GitHub Actions setup   |
-| [ci/vercel.md](./references/ci/vercel.md)                 | Vercel deployment, turbo-ignore |
-| [ci/patterns.md](./references/ci/patterns.md)             | --affected, caching strategies  |
-
-### CLI
-
-| File                                            | Purpose                                       |
-| ----------------------------------------------- | --------------------------------------------- |
-| [cli/RULE.md](./references/cli/RULE.md)         | turbo run basics                              |
-| [cli/commands.md](./references/cli/commands.md) | turbo run flags, turbo-ignore, other commands |
-
-### Best Practices
-
-| File                                                                          | Purpose                                                         |
-| ----------------------------------------------------------------------------- | --------------------------------------------------------------- |
-| [best-practices/RULE.md](./references/best-practices/RULE.md)                 | Monorepo best practices overview                                |
-| [best-practices/structure.md](./references/best-practices/structure.md)       | Repository structure, workspace config, TypeScript/ESLint setup |
-| [best-practices/packages.md](./references/best-practices/packages.md)         | Creating internal packages, JIT vs Compiled, exports            |
-| [best-practices/dependencies.md](./references/best-practices/dependencies.md) | Dependency management, installing, version sync                 |
-
-### Watch Mode
-
-| File                                        | Purpose                                         |
-| ------------------------------------------- | ----------------------------------------------- |
-| [watch/RULE.md](./references/watch/RULE.md) | turbo watch, interruptible tasks, dev workflows |
-
-### Boundaries (Experimental)
-
-| File                                                  | Purpose                                               |
-| ----------------------------------------------------- | ----------------------------------------------------- |
-| [boundaries/RULE.md](./references/boundaries/RULE.md) | Enforce package isolation, tag-based dependency rules |
-
-## Source Documentation
-
-This skill is based on the official Turborepo documentation at:
-
-- Source: `apps/docs/content/docs/` in the Turborepo repository
-- Live: https://turborepo.dev/docs

+ 0 - 70
neo/.agents/skills/turborepo/command/turborepo.md

@@ -1,70 +0,0 @@
----
-description: Load Turborepo skill for creating workflows, tasks, and pipelines in monorepos. Use when users ask to "create a workflow", "make a task", "generate a pipeline", or set up build orchestration.
----
-
-Load the Turborepo skill and help with monorepo task orchestration: creating workflows, configuring tasks, setting up pipelines, and optimizing builds.
-
-## Workflow
-
-### Step 1: Load turborepo skill
-
-```
-skill({ name: 'turborepo' })
-```
-
-### Step 2: Identify task type from user request
-
-Analyze $ARGUMENTS to determine:
-
-- **Topic**: configuration, caching, filtering, environment, CI, or CLI
-- **Task type**: new setup, debugging, optimization, or implementation
-
-Use decision trees in SKILL.md to select the relevant reference files.
-
-### Step 3: Read relevant reference files
-
-Based on task type, read from `references/<topic>/`:
-
-| Task                 | Files to Read                                           |
-| -------------------- | ------------------------------------------------------- |
-| Configure turbo.json | `configuration/RULE.md` + `configuration/tasks.md`      |
-| Debug cache issues   | `caching/gotchas.md`                                    |
-| Set up remote cache  | `caching/remote-cache.md`                               |
-| Filter packages      | `filtering/RULE.md` + `filtering/patterns.md`           |
-| Environment problems | `environment/gotchas.md` + `environment/modes.md`       |
-| Set up CI            | `ci/RULE.md` + `ci/github-actions.md` or `ci/vercel.md` |
-| CLI usage            | `cli/commands.md`                                       |
-
-### Step 4: Execute task
-
-Apply Turborepo-specific patterns from references to complete the user's request.
-
-**CRITICAL - When creating tasks/scripts/pipelines:**
-
-1. **DO NOT create Root Tasks** - Always create package tasks
-2. Add scripts to each relevant package's `package.json` (e.g., `apps/web/package.json`, `packages/ui/package.json`)
-3. Register the task in root `turbo.json`
-4. Root `package.json` only contains `turbo run <task>` - never actual task logic
-
-**Other things to verify:**
-
-- `outputs` defined for cacheable tasks
-- `dependsOn` uses correct syntax (`^task` vs `task`)
-- Environment variables in `env` key
-- `.env` files in `inputs` if used
-- Use `turbo run` (not `turbo`) in package.json and CI
-
-### Step 5: Summarize
-
-```
-=== Turborepo Task Complete ===
-
-Topic: <configuration|caching|filtering|environment|ci|cli>
-Files referenced: <reference files consulted>
-
-<brief summary of what was done>
-```
-
-<user-request>
-$ARGUMENTS
-</user-request>

+ 0 - 241
neo/.agents/skills/turborepo/references/best-practices/RULE.md

@@ -1,241 +0,0 @@
-# Monorepo Best Practices
-
-Essential patterns for structuring and maintaining a healthy Turborepo monorepo.
-
-## Repository Structure
-
-### Standard Layout
-
-```
-my-monorepo/
-├── apps/                    # Application packages (deployable)
-│   ├── web/
-│   ├── docs/
-│   └── api/
-├── packages/                # Library packages (shared code)
-│   ├── ui/
-│   ├── utils/
-│   └── config-*/           # Shared configs (eslint, typescript, etc.)
-├── package.json            # Root package.json (minimal deps)
-├── turbo.json              # Turborepo configuration
-├── pnpm-workspace.yaml     # (pnpm) or workspaces in package.json
-└── pnpm-lock.yaml          # Lockfile (required)
-```
-
-### Key Principles
-
-1. **`apps/` for deployables**: Next.js sites, APIs, CLIs - things that get deployed
-2. **`packages/` for libraries**: Shared code consumed by apps or other packages
-3. **One purpose per package**: Each package should do one thing well
-4. **No nested packages**: Don't put packages inside packages
-
-## Package Types
-
-### Application Packages (`apps/`)
-
-- **Deployable**: These are the "endpoints" of your package graph
-- **Not installed by other packages**: Apps shouldn't be dependencies of other packages
-- **No shared code**: If code needs sharing, extract to `packages/`
-
-```json
-// apps/web/package.json
-{
-  "name": "web",
-  "private": true,
-  "dependencies": {
-    "@repo/ui": "workspace:*",
-    "next": "latest"
-  }
-}
-```
-
-### Library Packages (`packages/`)
-
-- **Shared code**: Utilities, components, configs
-- **Namespaced names**: Use `@repo/` or `@yourorg/` prefix
-- **Clear exports**: Define what the package exposes
-
-```json
-// packages/ui/package.json
-{
-  "name": "@repo/ui",
-  "exports": {
-    "./button": "./src/button.tsx",
-    "./card": "./src/card.tsx"
-  }
-}
-```
-
-## Package Compilation Strategies
-
-### Just-in-Time (Simplest)
-
-Export TypeScript directly; let the app's bundler compile it.
-
-```json
-{
-  "name": "@repo/ui",
-  "exports": {
-    "./button": "./src/button.tsx"
-  }
-}
-```
-
-**Pros**: Zero build config, instant changes
-**Cons**: Can't cache builds, requires app bundler support
-
-### Compiled (Recommended for Libraries)
-
-Package compiles itself with `tsc` or bundler.
-
-```json
-{
-  "name": "@repo/ui",
-  "exports": {
-    "./button": {
-      "types": "./src/button.tsx",
-      "default": "./dist/button.js"
-    }
-  },
-  "scripts": {
-    "build": "tsc"
-  }
-}
-```
-
-**Pros**: Cacheable by Turborepo, works everywhere
-**Cons**: More configuration
-
-## Dependency Management
-
-### Install Where Used
-
-Install dependencies in the package that uses them, not the root.
-
-```bash
-# Good: Install in the package that needs it
-pnpm add lodash --filter=@repo/utils
-
-# Avoid: Installing everything at root
-pnpm add lodash -w  # Only for repo-level tools
-```
-
-### Root Dependencies
-
-Only these belong in root `package.json`:
-
-- `turbo` - The build system
-- `husky`, `lint-staged` - Git hooks
-- Repository-level tooling
-
-### Internal Dependencies
-
-Use workspace protocol for internal packages:
-
-```json
-// pnpm/bun
-{ "@repo/ui": "workspace:*" }
-
-// npm/yarn
-{ "@repo/ui": "*" }
-```
-
-## Exports Best Practices
-
-### Use `exports` Field (Not `main`)
-
-```json
-{
-  "exports": {
-    ".": "./src/index.ts",
-    "./button": "./src/button.tsx",
-    "./utils": "./src/utils.ts"
-  }
-}
-```
-
-### Avoid Barrel Files
-
-Don't create `index.ts` files that re-export everything:
-
-```typescript
-// BAD: packages/ui/src/index.ts
-export * from './button';
-export * from './card';
-export * from './modal';
-// ... imports everything even if you need one thing
-
-// GOOD: Direct exports in package.json
-{
-  "exports": {
-    "./button": "./src/button.tsx",
-    "./card": "./src/card.tsx"
-  }
-}
-```
-
-### Namespace Your Packages
-
-```json
-// Good
-{ "name": "@repo/ui" }
-{ "name": "@acme/utils" }
-
-// Avoid (conflicts with npm registry)
-{ "name": "ui" }
-{ "name": "utils" }
-```
-
-## Common Anti-Patterns
-
-### Accessing Files Across Package Boundaries
-
-```typescript
-// BAD: Reaching into another package
-import { Button } from "../../packages/ui/src/button";
-
-// GOOD: Install and import properly
-import { Button } from "@repo/ui/button";
-```
-
-### Shared Code in Apps
-
-```
-// BAD
-apps/
-  web/
-    shared/        # This should be a package!
-      utils.ts
-
-// GOOD
-packages/
-  utils/           # Proper shared package
-    src/utils.ts
-```
-
-### Too Many Root Dependencies
-
-```json
-// BAD: Root has app dependencies
-{
-  "dependencies": {
-    "react": "^18",
-    "next": "^14",
-    "lodash": "^4"
-  }
-}
-
-// GOOD: Root only has repo tools
-{
-  "devDependencies": {
-    "turbo": "latest",
-    "husky": "latest"
-  }
-}
-```
-
-## See Also
-
-- [structure.md](./structure.md) - Detailed repository structure patterns
-- [packages.md](./packages.md) - Creating and managing internal packages
-- [dependencies.md](./dependencies.md) - Dependency management strategies

+ 0 - 246
neo/.agents/skills/turborepo/references/best-practices/dependencies.md

@@ -1,246 +0,0 @@
-# Dependency Management
-
-Best practices for managing dependencies in a Turborepo monorepo.
-
-## Core Principle: Install Where Used
-
-Dependencies belong in the package that uses them, not the root.
-
-```bash
-# Good: Install in specific package
-pnpm add react --filter=@repo/ui
-pnpm add next --filter=web
-
-# Avoid: Installing in root
-pnpm add react -w  # Only for repo-level tools!
-```
-
-## Benefits of Local Installation
-
-### 1. Clarity
-
-Each package's `package.json` lists exactly what it needs:
-
-```json
-// packages/ui/package.json
-{
-  "dependencies": {
-    "react": "^18.0.0",
-    "class-variance-authority": "^0.7.0"
-  }
-}
-```
-
-### 2. Flexibility
-
-Different packages can use different versions when needed:
-
-```json
-// packages/legacy-ui/package.json
-{ "dependencies": { "react": "^17.0.0" } }
-
-// packages/ui/package.json
-{ "dependencies": { "react": "^18.0.0" } }
-```
-
-### 3. Better Caching
-
-Installing in root changes workspace lockfile, invalidating all caches.
-
-### 4. Pruning Support
-
-`turbo prune` can remove unused dependencies for Docker images.
-
-## What Belongs in Root
-
-Only repository-level tools:
-
-```json
-// Root package.json
-{
-  "devDependencies": {
-    "turbo": "latest",
-    "husky": "^8.0.0",
-    "lint-staged": "^15.0.0"
-  }
-}
-```
-
-**NOT** application dependencies:
-
-- react, next, express
-- lodash, axios, zod
-- Testing libraries (unless truly repo-wide)
-
-## Installing Dependencies
-
-### Single Package
-
-```bash
-# pnpm
-pnpm add lodash --filter=@repo/utils
-
-# npm
-npm install lodash --workspace=@repo/utils
-
-# yarn
-yarn workspace @repo/utils add lodash
-
-# bun
-cd packages/utils && bun add lodash
-```
-
-### Multiple Packages
-
-```bash
-# pnpm
-pnpm add jest --save-dev --filter=web --filter=@repo/ui
-
-# npm
-npm install jest --save-dev --workspace=web --workspace=@repo/ui
-
-# yarn (v2+)
-yarn workspaces foreach -R --from '{web,@repo/ui}' add jest --dev
-```
-
-### Internal Packages
-
-```bash
-# pnpm
-pnpm add @repo/ui --filter=web
-
-# This updates package.json:
-{
-  "dependencies": {
-    "@repo/ui": "workspace:*"
-  }
-}
-```
-
-## Keeping Versions in Sync
-
-### Option 1: Tooling
-
-```bash
-# syncpack - Check and fix version mismatches
-npx syncpack list-mismatches
-npx syncpack fix-mismatches
-
-# manypkg - Similar functionality
-npx @manypkg/cli check
-npx @manypkg/cli fix
-
-# sherif - Rust-based, very fast
-npx sherif
-```
-
-### Option 2: Package Manager Commands
-
-```bash
-# pnpm - Update everywhere
-pnpm up --recursive typescript@latest
-
-# npm - Update in all workspaces
-npm install typescript@latest --workspaces
-```
-
-### Option 3: pnpm Catalogs (pnpm 9.5+)
-
-```yaml
-# pnpm-workspace.yaml
-packages:
-  - "apps/*"
-  - "packages/*"
-
-catalog:
-  react: ^18.2.0
-  typescript: ^5.3.0
-```
-
-```json
-// Any package.json
-{
-  "dependencies": {
-    "react": "catalog:" // Uses version from catalog
-  }
-}
-```
-
-## Internal vs External Dependencies
-
-### Internal (Workspace)
-
-```json
-// pnpm/bun
-{ "@repo/ui": "workspace:*" }
-
-// npm/yarn
-{ "@repo/ui": "*" }
-```
-
-Turborepo understands these relationships and orders builds accordingly.
-
-### External (npm Registry)
-
-```json
-{ "lodash": "^4.17.21" }
-```
-
-Standard semver versioning from npm.
-
-## Peer Dependencies
-
-For library packages that expect the consumer to provide dependencies:
-
-```json
-// packages/ui/package.json
-{
-  "peerDependencies": {
-    "react": "^18.0.0",
-    "react-dom": "^18.0.0"
-  },
-  "devDependencies": {
-    "react": "^18.0.0", // For development/testing
-    "react-dom": "^18.0.0"
-  }
-}
-```
-
-## Common Issues
-
-### "Module not found"
-
-1. Check the dependency is installed in the right package
-2. Run `pnpm install` / `npm install` to update lockfile
-3. Check exports are defined in the package
-
-### Version Conflicts
-
-Packages can use different versions - this is a feature, not a bug. But if you need consistency:
-
-1. Use tooling (syncpack, manypkg)
-2. Use pnpm catalogs
-3. Create a lint rule
-
-### Hoisting Issues
-
-Some tools expect dependencies in specific locations. Use package manager config:
-
-```yaml
-# .npmrc (pnpm)
-public-hoist-pattern[]=*eslint*
-public-hoist-pattern[]=*prettier*
-```
-
-## Lockfile
-
-**Required** for:
-
-- Reproducible builds
-- Turborepo dependency analysis
-- Cache correctness
-
-```bash
-# Commit your lockfile!
-git add pnpm-lock.yaml  # or package-lock.json, yarn.lock
-```

+ 0 - 335
neo/.agents/skills/turborepo/references/best-practices/packages.md

@@ -1,335 +0,0 @@
-# Creating Internal Packages
-
-How to create and structure internal packages in your monorepo.
-
-## Package Creation Checklist
-
-1. Create directory in `packages/`
-2. Add `package.json` with name and exports
-3. Add source code in `src/`
-4. Add `tsconfig.json` if using TypeScript
-5. Install as dependency in consuming packages
-6. Run package manager install to update lockfile
-
-## Package Compilation Strategies
-
-### Just-in-Time (JIT)
-
-Export TypeScript directly. The consuming app's bundler compiles it.
-
-```json
-// packages/ui/package.json
-{
-  "name": "@repo/ui",
-  "exports": {
-    "./button": "./src/button.tsx",
-    "./card": "./src/card.tsx"
-  },
-  "scripts": {
-    "lint": "eslint .",
-    "check-types": "tsc --noEmit"
-  }
-}
-```
-
-**When to use:**
-
-- Apps use modern bundlers (Turbopack, webpack, Vite)
-- You want minimal configuration
-- Build times are acceptable without caching
-
-**Limitations:**
-
-- No Turborepo cache for the package itself
-- Consumer must support TypeScript compilation
-- Can't use TypeScript `paths` (use Node.js subpath imports instead)
-
-### Compiled
-
-Package handles its own compilation.
-
-```json
-// packages/ui/package.json
-{
-  "name": "@repo/ui",
-  "exports": {
-    "./button": {
-      "types": "./src/button.tsx",
-      "default": "./dist/button.js"
-    }
-  },
-  "scripts": {
-    "build": "tsc",
-    "dev": "tsc --watch"
-  }
-}
-```
-
-```json
-// packages/ui/tsconfig.json
-{
-  "extends": "@repo/typescript-config/library.json",
-  "compilerOptions": {
-    "outDir": "dist",
-    "rootDir": "src"
-  },
-  "include": ["src"],
-  "exclude": ["node_modules", "dist"]
-}
-```
-
-**When to use:**
-
-- You want Turborepo to cache builds
-- Package will be used by non-bundler tools
-- You need maximum compatibility
-
-**Remember:** Add `dist/**` to turbo.json outputs!
-
-## Defining Exports
-
-### Multiple Entrypoints
-
-```json
-{
-  "exports": {
-    ".": "./src/index.ts", // @repo/ui
-    "./button": "./src/button.tsx", // @repo/ui/button
-    "./card": "./src/card.tsx", // @repo/ui/card
-    "./hooks": "./src/hooks/index.ts" // @repo/ui/hooks
-  }
-}
-```
-
-### Conditional Exports (Compiled)
-
-```json
-{
-  "exports": {
-    "./button": {
-      "types": "./src/button.tsx",
-      "import": "./dist/button.mjs",
-      "require": "./dist/button.cjs",
-      "default": "./dist/button.js"
-    }
-  }
-}
-```
-
-## Installing Internal Packages
-
-### Add to Consuming Package
-
-```json
-// apps/web/package.json
-{
-  "dependencies": {
-    "@repo/ui": "workspace:*" // pnpm/bun
-    // "@repo/ui": "*"         // npm/yarn
-  }
-}
-```
-
-### Run Install
-
-```bash
-pnpm install  # Updates lockfile with new dependency
-```
-
-### Import and Use
-
-```typescript
-// apps/web/src/page.tsx
-import { Button } from '@repo/ui/button';
-
-export default function Page() {
-  return <Button>Click me</Button>;
-}
-```
-
-## One Purpose Per Package
-
-### Good Examples
-
-```
-packages/
-├── ui/                  # Shared UI components
-├── utils/               # General utilities
-├── auth/                # Authentication logic
-├── database/            # Database client/schemas
-├── eslint-config/       # ESLint configuration
-├── typescript-config/   # TypeScript configuration
-└── api-client/          # Generated API client
-```
-
-### Avoid Mega-Packages
-
-```
-// BAD: One package for everything
-packages/
-└── shared/
-    ├── components/
-    ├── utils/
-    ├── hooks/
-    ├── types/
-    └── api/
-
-// GOOD: Separate by purpose
-packages/
-├── ui/          # Components
-├── utils/       # Utilities
-├── hooks/       # React hooks
-├── types/       # Shared TypeScript types
-└── api-client/  # API utilities
-```
-
-## Config Packages
-
-### TypeScript Config
-
-```json
-// packages/typescript-config/package.json
-{
-  "name": "@repo/typescript-config",
-  "exports": {
-    "./base.json": "./base.json",
-    "./nextjs.json": "./nextjs.json",
-    "./library.json": "./library.json"
-  }
-}
-```
-
-### ESLint Config
-
-```json
-// packages/eslint-config/package.json
-{
-  "name": "@repo/eslint-config",
-  "exports": {
-    "./base": "./base.js",
-    "./next": "./next.js"
-  },
-  "dependencies": {
-    "eslint": "^8.0.0",
-    "eslint-config-next": "latest"
-  }
-}
-```
-
-## Common Mistakes
-
-### Forgetting to Export
-
-```json
-// BAD: No exports defined
-{
-  "name": "@repo/ui"
-}
-
-// GOOD: Clear exports
-{
-  "name": "@repo/ui",
-  "exports": {
-    "./button": "./src/button.tsx"
-  }
-}
-```
-
-### Wrong Workspace Syntax
-
-```json
-// pnpm/bun
-{ "@repo/ui": "workspace:*" }  // Correct
-
-// npm/yarn
-{ "@repo/ui": "*" }            // Correct
-{ "@repo/ui": "workspace:*" }  // Wrong for npm/yarn!
-```
-
-### Missing from turbo.json Outputs
-
-```json
-// Package builds to dist/, but turbo.json doesn't know
-{
-  "tasks": {
-    "build": {
-      "outputs": [".next/**"]  // Missing dist/**!
-    }
-  }
-}
-
-// Correct
-{
-  "tasks": {
-    "build": {
-      "outputs": [".next/**", "dist/**"]
-    }
-  }
-}
-```
-
-## TypeScript Best Practices
-
-### Use Node.js Subpath Imports (Not `paths`)
-
-TypeScript `compilerOptions.paths` breaks with JIT packages. Use Node.js subpath imports instead (TypeScript 5.4+).
-
-**JIT Package:**
-
-```json
-// packages/ui/package.json
-{
-  "imports": {
-    "#*": "./src/*"
-  }
-}
-```
-
-```typescript
-// packages/ui/button.tsx
-import { MY_STRING } from "#utils.ts"; // Uses .ts extension
-```
-
-**Compiled Package:**
-
-```json
-// packages/ui/package.json
-{
-  "imports": {
-    "#*": "./dist/*"
-  }
-}
-```
-
-```typescript
-// packages/ui/button.tsx
-import { MY_STRING } from "#utils.js"; // Uses .js extension
-```
-
-### Use `tsc` for Internal Packages
-
-For internal packages, prefer `tsc` over bundlers. Bundlers can mangle code before it reaches your app's bundler, causing hard-to-debug issues.
-
-### Enable Go-to-Definition
-
-For Compiled Packages, enable declaration maps:
-
-```json
-// tsconfig.json
-{
-  "compilerOptions": {
-    "declaration": true,
-    "declarationMap": true
-  }
-}
-```
-
-This creates `.d.ts` and `.d.ts.map` files for IDE navigation.
-
-### No Root tsconfig.json Needed
-
-Each package should have its own `tsconfig.json`. A root one causes all tasks to miss cache when changed. Only use root `tsconfig.json` for non-package scripts.
-
-### Avoid TypeScript Project References
-
-They add complexity and another caching layer. Turborepo handles dependencies better.

+ 0 - 297
neo/.agents/skills/turborepo/references/best-practices/structure.md

@@ -1,297 +0,0 @@
-# Repository Structure
-
-Detailed guidance on structuring a Turborepo monorepo.
-
-## Workspace Configuration
-
-### pnpm (Recommended)
-
-```yaml
-# pnpm-workspace.yaml
-packages:
-  - "apps/*"
-  - "packages/*"
-```
-
-### npm/yarn/bun
-
-```json
-// package.json
-{
-  "workspaces": ["apps/*", "packages/*"]
-}
-```
-
-## Root package.json
-
-```json
-{
-  "name": "my-monorepo",
-  "private": true,
-  "packageManager": "pnpm@9.0.0",
-  "scripts": {
-    "build": "turbo run build",
-    "dev": "turbo run dev",
-    "lint": "turbo run lint",
-    "test": "turbo run test"
-  },
-  "devDependencies": {
-    "turbo": "latest"
-  }
-}
-```
-
-Key points:
-
-- `private: true` - Prevents accidental publishing
-- `packageManager` - Enforces consistent package manager version
-- **Scripts only delegate to `turbo run`** - No actual build logic here!
-- Minimal devDependencies (just turbo and repo tools)
-
-## Always Prefer Package Tasks
-
-**Always use package tasks. Only use Root Tasks if you cannot succeed with package tasks.**
-
-```json
-// packages/web/package.json
-{
-  "scripts": {
-    "build": "next build",
-    "lint": "eslint .",
-    "test": "vitest",
-    "typecheck": "tsc --noEmit"
-  }
-}
-
-// packages/api/package.json
-{
-  "scripts": {
-    "build": "tsc",
-    "lint": "eslint .",
-    "test": "vitest",
-    "typecheck": "tsc --noEmit"
-  }
-}
-```
-
-Package tasks enable Turborepo to:
-
-1. **Parallelize** - Run `web#lint` and `api#lint` simultaneously
-2. **Cache individually** - Each package's task output is cached separately
-3. **Filter precisely** - Run `turbo run test --filter=web` for just one package
-
-**Root Tasks are a fallback** for tasks that truly cannot run per-package:
-
-```json
-// AVOID unless necessary - sequential, not parallelized, can't filter
-{
-  "scripts": {
-    "lint": "eslint apps/web && eslint apps/api && eslint packages/ui"
-  }
-}
-```
-
-## Root turbo.json
-
-```json
-{
-  "$schema": "https://v2-8-21-canary-9.turborepo.dev/schema.json",
-  "tasks": {
-    "build": {
-      "dependsOn": ["^build"],
-      "outputs": ["dist/**", ".next/**", "!.next/cache/**"]
-    },
-    "lint": {},
-    "test": {
-      "dependsOn": ["build"]
-    },
-    "dev": {
-      "cache": false,
-      "persistent": true
-    }
-  }
-}
-```
-
-With `futureFlags.globalConfiguration`, global settings move under a `global` key:
-
-```json
-{
-  "$schema": "https://v2-8-21-canary-9.turborepo.dev/schema.json",
-  "futureFlags": { "globalConfiguration": true },
-  "global": {
-    "inputs": ["tsconfig.json"],
-    "env": ["CI"]
-  },
-  "tasks": {
-    "build": {
-      "dependsOn": ["^build"],
-      "outputs": ["dist/**", ".next/**", "!.next/cache/**"]
-    },
-    "lint": {},
-    "test": {
-      "dependsOn": ["build"]
-    },
-    "dev": {
-      "cache": false,
-      "persistent": true
-    }
-  }
-}
-```
-
-## Directory Organization
-
-### Grouping Packages
-
-You can group packages by adding more workspace paths:
-
-```yaml
-# pnpm-workspace.yaml
-packages:
-  - "apps/*"
-  - "packages/*"
-  - "packages/config/*" # Grouped configs
-  - "packages/features/*" # Feature packages
-```
-
-This allows:
-
-```
-packages/
-├── ui/
-├── utils/
-├── config/
-│   ├── eslint/
-│   ├── typescript/
-│   └── tailwind/
-└── features/
-    ├── auth/
-    └── payments/
-```
-
-### What NOT to Do
-
-```yaml
-# BAD: Nested wildcards cause ambiguous behavior
-packages:
-  - "packages/**" # Don't do this!
-```
-
-## Package Anatomy
-
-### Minimum Required Files
-
-```
-packages/ui/
-├── package.json    # Required: Makes it a package
-├── src/            # Source code
-│   └── button.tsx
-└── tsconfig.json   # TypeScript config (if using TS)
-```
-
-### package.json Requirements
-
-```json
-{
-  "name": "@repo/ui", // Unique, namespaced name
-  "version": "0.0.0", // Version (can be 0.0.0 for internal)
-  "private": true, // Prevents accidental publishing
-  "exports": {
-    // Entry points
-    "./button": "./src/button.tsx"
-  }
-}
-```
-
-## TypeScript Configuration
-
-### Shared Base Config
-
-Create a shared TypeScript config package:
-
-```
-packages/
-└── typescript-config/
-    ├── package.json
-    ├── base.json
-    ├── nextjs.json
-    └── library.json
-```
-
-```json
-// packages/typescript-config/base.json
-{
-  "compilerOptions": {
-    "strict": true,
-    "esModuleInterop": true,
-    "skipLibCheck": true,
-    "moduleResolution": "bundler",
-    "module": "ESNext",
-    "target": "ES2022"
-  }
-}
-```
-
-### Extending in Packages
-
-```json
-// packages/ui/tsconfig.json
-{
-  "extends": "@repo/typescript-config/library.json",
-  "compilerOptions": {
-    "outDir": "dist",
-    "rootDir": "src"
-  },
-  "include": ["src"],
-  "exclude": ["node_modules", "dist"]
-}
-```
-
-### No Root tsconfig.json
-
-You likely don't need a `tsconfig.json` in the workspace root. Each package should have its own config extending from the shared config package.
-
-## ESLint Configuration
-
-### Shared Config Package
-
-```
-packages/
-└── eslint-config/
-    ├── package.json
-    ├── base.js
-    ├── next.js
-    └── library.js
-```
-
-```json
-// packages/eslint-config/package.json
-{
-  "name": "@repo/eslint-config",
-  "exports": {
-    "./base": "./base.js",
-    "./next": "./next.js",
-    "./library": "./library.js"
-  }
-}
-```
-
-### Using in Packages
-
-```js
-// apps/web/.eslintrc.js
-module.exports = {
-  extends: ["@repo/eslint-config/next"]
-};
-```
-
-## Lockfile
-
-A lockfile is **required** for:
-
-- Reproducible builds
-- Turborepo to understand package dependencies
-- Cache correctness
-
-Without a lockfile, you'll see unpredictable behavior.

+ 0 - 126
neo/.agents/skills/turborepo/references/boundaries/RULE.md

@@ -1,126 +0,0 @@
-# Boundaries
-
-**Experimental feature** - See [RFC](https://github.com/vercel/turborepo/discussions/9435)
-
-Full docs: https://turborepo.dev/docs/reference/boundaries
-
-Boundaries enforce package isolation by detecting:
-
-1. Imports of files outside the package's directory
-2. Imports of packages not declared in `package.json` dependencies
-
-## Usage
-
-```bash
-turbo boundaries
-```
-
-Run this to check for workspace violations across your monorepo.
-
-## Tags
-
-Tags allow you to create rules for which packages can depend on each other.
-
-### Adding Tags to a Package
-
-```json
-// packages/ui/turbo.json
-{
-  "tags": ["internal"]
-}
-```
-
-### Configuring Tag Rules
-
-Rules go in root `turbo.json`:
-
-```json
-// turbo.json
-{
-  "boundaries": {
-    "tags": {
-      "public": {
-        "dependencies": {
-          "deny": ["internal"]
-        }
-      }
-    }
-  }
-}
-```
-
-This prevents `public`-tagged packages from importing `internal`-tagged packages.
-
-### Rule Types
-
-**Allow-list approach** (only allow specific tags):
-
-```json
-{
-  "boundaries": {
-    "tags": {
-      "public": {
-        "dependencies": {
-          "allow": ["public"]
-        }
-      }
-    }
-  }
-}
-```
-
-**Deny-list approach** (block specific tags):
-
-```json
-{
-  "boundaries": {
-    "tags": {
-      "public": {
-        "dependencies": {
-          "deny": ["internal"]
-        }
-      }
-    }
-  }
-}
-```
-
-**Restrict dependents** (who can import this package):
-
-```json
-{
-  "boundaries": {
-    "tags": {
-      "private": {
-        "dependents": {
-          "deny": ["public"]
-        }
-      }
-    }
-  }
-}
-```
-
-### Using Package Names
-
-Package names work in place of tags:
-
-```json
-{
-  "boundaries": {
-    "tags": {
-      "private": {
-        "dependents": {
-          "deny": ["@repo/my-pkg"]
-        }
-      }
-    }
-  }
-}
-```
-
-## Key Points
-
-- Rules apply transitively (dependencies of dependencies)
-- Helps enforce architectural boundaries at scale
-- Catches violations before runtime/build errors

+ 0 - 153
neo/.agents/skills/turborepo/references/caching/RULE.md

@@ -1,153 +0,0 @@
-# How Turborepo Caching Works
-
-Turborepo's core principle: **never do the same work twice**.
-
-## The Cache Equation
-
-```
-fingerprint(inputs) → stored outputs
-```
-
-If inputs haven't changed, restore outputs from cache instead of re-running the task.
-
-## What Determines the Cache Key
-
-### Global Hash Inputs
-
-These affect ALL tasks in the repo:
-
-- `package-lock.json` / `yarn.lock` / `pnpm-lock.yaml`
-- Files listed in `globalDependencies` (or `global.env` when using `globalConfiguration`)
-- Environment variables in `globalEnv` (or `global.env`)
-- `turbo.json` configuration
-
-```json
-{
-  "globalDependencies": [".env", "tsconfig.base.json"],
-  "globalEnv": ["CI", "NODE_ENV"]
-}
-```
-
-### Task Hash Inputs
-
-These affect specific tasks:
-
-- All files in the package (unless filtered by `inputs`)
-- `package.json` contents
-- Environment variables in task's `env` key
-- Task configuration (command, outputs, dependencies)
-- Hashes of dependent tasks (`dependsOn`)
-- Files from `global.inputs` (when using `futureFlags.globalConfiguration` — see below)
-
-```json
-{
-  "tasks": {
-    "build": {
-      "dependsOn": ["^build"],
-      "inputs": ["src/**", "package.json", "tsconfig.json"],
-      "env": ["API_URL"]
-    }
-  }
-}
-```
-
-### How `global.inputs` Changes the Hash Equation
-
-When `futureFlags.globalConfiguration` is enabled, `global.inputs` files are **not** part of the global hash. Instead, they are prepended to every task's `inputs` and folded into the **task hash**. This is a fundamental change from `globalDependencies`.
-
-**With `globalDependencies` (default):**
-
-```
-task cache key = hash(global hash, task hash)
-                      ↑ includes globalDependencies file hashes
-```
-
-Changing a `globalDependencies` file invalidates **every** task, regardless of task-level `inputs`. There is no way for a task to opt out.
-
-**With `global.inputs` (`futureFlags.globalConfiguration`):**
-
-```
-task cache key = hash(global hash, task hash)
-                                   ↑ includes global.inputs file hashes (merged with task inputs)
-```
-
-`global.inputs` files are merged into each task's input globs. This means:
-
-- Tasks can **exclude** specific global files with negation globs: `"inputs": ["$TURBO_DEFAULT$", "!$TURBO_ROOT$/tsconfig.json"]`
-- The global hash is smaller (it still includes lockfile, engines, `global.env`, etc. — but not file hashes from `global.inputs`)
-- The task hash correctly includes the global input file hashes alongside the task's own inputs
-
-```json
-{
-  "futureFlags": { "globalConfiguration": true },
-  "global": {
-    "inputs": ["tsconfig.json", ".env"]
-  },
-  "tasks": {
-    "build": {
-      "outputs": ["dist/**"]
-    },
-    "lint": {
-      "inputs": ["$TURBO_DEFAULT$", "!$TURBO_ROOT$/tsconfig.json"]
-    }
-  }
-}
-```
-
-In this example, changing `tsconfig.json` invalidates `build` (it's in the task's inputs) but **not** `lint` (which explicitly excludes it). With `globalDependencies`, both would have been invalidated.
-
-## What Gets Cached
-
-1. **File outputs** - files/directories specified in `outputs`
-2. **Task logs** - stdout/stderr for replay on cache hit
-
-```json
-{
-  "tasks": {
-    "build": {
-      "outputs": ["dist/**", ".next/**"]
-    }
-  }
-}
-```
-
-## Local Cache Location
-
-```
-.turbo/cache/
-├── <hash1>.tar.zst    # compressed outputs
-├── <hash2>.tar.zst
-└── ...
-```
-
-Add `.turbo` to `.gitignore`.
-
-## Cache Restoration
-
-On cache hit, Turborepo:
-
-1. Extracts archived outputs to their original locations
-2. Replays the logged stdout/stderr
-3. Reports the task as cached (shows `FULL TURBO` in output)
-
-## Example Flow
-
-```bash
-# First run - executes build, caches result
-turbo build
-# → packages/ui: cache miss, executing...
-# → packages/web: cache miss, executing...
-
-# Second run - same inputs, restores from cache
-turbo build
-# → packages/ui: cache hit, replaying output
-# → packages/web: cache hit, replaying output
-# → FULL TURBO
-```
-
-## Key Points
-
-- Cache is content-addressed (based on input hash, not timestamps)
-- Empty `outputs` array means task runs but nothing is cached
-- Tasks without `outputs` key cache nothing (use `"outputs": []` to be explicit)
-- Cache is invalidated when ANY input changes

+ 0 - 190
neo/.agents/skills/turborepo/references/caching/gotchas.md

@@ -1,190 +0,0 @@
-# Debugging Cache Issues
-
-## Diagnostic Tools
-
-### `--summarize`
-
-Generates a JSON file with all hash inputs. Compare two runs to find differences.
-
-```bash
-turbo build --summarize
-# Creates .turbo/runs/<run-id>.json
-```
-
-The summary includes:
-
-- Global hash and its inputs
-- Per-task hashes and their inputs
-- Environment variables that affected the hash
-
-**Comparing runs:**
-
-```bash
-# Run twice, compare the summaries
-diff .turbo/runs/<first-run>.json .turbo/runs/<second-run>.json
-```
-
-### `--dry` / `--dry=json`
-
-See what would run without executing anything:
-
-```bash
-turbo build --dry
-turbo build --dry=json  # machine-readable output
-```
-
-Shows cache status for each task without running them.
-
-### `--force`
-
-Skip reading cache, re-execute all tasks:
-
-```bash
-turbo build --force
-```
-
-Useful to verify tasks actually work (not just cached results).
-
-## Unexpected Cache Misses
-
-**Symptom:** Task runs when you expected a cache hit.
-
-### Environment Variable Changed
-
-Check if an env var in the `env` key changed:
-
-```json
-{
-  "tasks": {
-    "build": {
-      "env": ["API_URL", "NODE_ENV"]
-    }
-  }
-}
-```
-
-Different `API_URL` between runs = cache miss.
-
-### .env File Changed
-
-`.env` files aren't tracked by default. Add to `inputs`:
-
-```json
-{
-  "tasks": {
-    "build": {
-      "inputs": ["$TURBO_DEFAULT$", ".env", ".env.local"]
-    }
-  }
-}
-```
-
-Or use `globalDependencies` for repo-wide env files:
-
-```json
-{
-  "globalDependencies": [".env"]
-}
-```
-
-With `futureFlags.globalConfiguration`, use `global.inputs` instead. The key difference: `global.inputs` files are folded into each task's hash individually (not the global hash), so tasks can exclude specific files with negation globs.
-
-```json
-{
-  "futureFlags": { "globalConfiguration": true },
-  "global": {
-    "inputs": [".env"]
-  }
-}
-```
-
-### Lockfile Changed
-
-Installing/updating packages changes the global hash.
-
-### Source Files Changed
-
-Any file in the package (or in `inputs`) triggers a miss.
-
-### turbo.json Changed
-
-Config changes invalidate the global hash.
-
-## Incorrect Cache Hits
-
-**Symptom:** Cached output is stale/wrong.
-
-### Missing Environment Variable
-
-Task uses an env var not listed in `env`:
-
-```javascript
-// build.js
-const apiUrl = process.env.API_URL; // not tracked!
-```
-
-Fix: add to task config:
-
-```json
-{
-  "tasks": {
-    "build": {
-      "env": ["API_URL"]
-    }
-  }
-}
-```
-
-### Missing File in Inputs
-
-Task reads a file outside default inputs:
-
-```json
-{
-  "tasks": {
-    "build": {
-      "inputs": [
-        "$TURBO_DEFAULT$",
-        "../../shared-config.json" // file outside package
-      ]
-    }
-  }
-}
-```
-
-## Useful Flags
-
-```bash
-# Only show output for cache misses
-turbo build --output-logs=new-only
-
-# Show output for everything (debugging)
-turbo build --output-logs=full
-
-# See why tasks are running
-turbo build --verbosity=2
-```
-
-## Debugging with `globalConfiguration` Enabled
-
-When `futureFlags.globalConfiguration` is on, `global.inputs` files appear in per-task hash inputs (not the global hash). If you're getting unexpected cache misses:
-
-1. Check `--summarize` output — global input files will show up in the **task inputs** section, not the global hash section
-2. Verify tasks aren't accidentally excluding global inputs via negation globs in `inputs`
-3. Remember that toggling the `globalConfiguration` flag itself invalidates all caches (the flag value is part of the global hash)
-
-If you're getting unexpected cache **hits** after changing a global input file, the task may be excluding that file with a negation glob. Check the task's `inputs` for `!$TURBO_ROOT$/...` patterns.
-
-## Quick Checklist
-
-Cache miss when expected hit:
-
-1. Run with `--summarize`, compare with previous run
-2. Check env vars with `--dry=json`
-3. Look for lockfile/config changes in git
-
-Cache hit when expected miss:
-
-1. Verify env var is in `env` array
-2. Verify file is in `inputs` array
-3. Check if file is outside package directory

+ 0 - 127
neo/.agents/skills/turborepo/references/caching/remote-cache.md

@@ -1,127 +0,0 @@
-# Remote Caching
-
-Share cache artifacts across your team and CI pipelines.
-
-## Benefits
-
-- Team members get cache hits from each other's work
-- CI gets cache hits from local development (and vice versa)
-- Dramatically faster CI runs after first build
-- No more "works on my machine" rebuilds
-
-## Vercel Remote Cache
-
-Free, zero-config when deploying on Vercel. For local dev and other CI:
-
-### Local Development Setup
-
-```bash
-# Authenticate with Vercel
-npx turbo login
-
-# Link repo to your Vercel team
-npx turbo link
-```
-
-This creates `.turbo/config.json` with your team info (gitignored by default).
-
-### CI Setup
-
-Set these environment variables:
-
-```bash
-TURBO_TOKEN=<your-token>
-TURBO_TEAM=<your-team-slug>
-```
-
-Get your token from Vercel dashboard → Settings → Tokens.
-
-**GitHub Actions example:**
-
-```yaml
-- name: Build
-  run: npx turbo build
-  env:
-    TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
-    TURBO_TEAM: ${{ vars.TURBO_TEAM }}
-```
-
-## Configuration in turbo.json
-
-```json
-{
-  "remoteCache": {
-    "enabled": true,
-    "signature": false
-  }
-}
-```
-
-Options:
-
-- `enabled`: toggle remote cache (default: true when authenticated)
-- `signature`: require artifact signing (default: false)
-
-## Artifact Signing
-
-Verify cache artifacts haven't been tampered with:
-
-```bash
-# Set a secret key (use same key across all environments)
-export TURBO_REMOTE_CACHE_SIGNATURE_KEY="your-secret-key"
-```
-
-Enable in config:
-
-```json
-{
-  "remoteCache": {
-    "signature": true
-  }
-}
-```
-
-Signed artifacts can only be restored if the signature matches.
-
-## Self-Hosted Options
-
-Community implementations for running your own cache server:
-
-- **turbo-remote-cache** (Node.js) - supports S3, GCS, Azure
-- **turborepo-remote-cache** (Go) - lightweight, S3-compatible
-- **ducktape** (Rust) - high-performance option
-
-Configure with environment variables:
-
-```bash
-TURBO_API=https://your-cache-server.com
-TURBO_TOKEN=your-auth-token
-TURBO_TEAM=your-team
-```
-
-## Cache Behavior Control
-
-```bash
-# Disable remote cache for a run
-turbo build --remote-cache-read-only  # read but don't write
-turbo build --no-cache                # skip cache entirely
-
-# Environment variable alternative
-TURBO_REMOTE_ONLY=true  # only use remote, skip local
-```
-
-## Debugging Remote Cache
-
-```bash
-# Verbose output shows cache operations
-turbo build --verbosity=2
-
-# Check if remote cache is configured
-turbo config
-```
-
-Look for:
-
-- "Remote caching enabled" in output
-- Upload/download messages during runs
-- "cache hit, replaying output" with remote cache indicator

+ 0 - 79
neo/.agents/skills/turborepo/references/ci/RULE.md

@@ -1,79 +0,0 @@
-# CI/CD with Turborepo
-
-General principles for running Turborepo in continuous integration environments.
-
-## Core Principles
-
-### Always Use `turbo run` in CI
-
-**Never use the `turbo <tasks>` shorthand in CI or scripts.** Always use `turbo run`:
-
-```bash
-# CORRECT - Always use in CI, package.json, scripts
-turbo run build test lint
-
-# WRONG - Shorthand is only for one-off terminal commands
-turbo build test lint
-```
-
-The shorthand `turbo <tasks>` is only for one-off invocations typed directly in terminal by humans or agents. Anywhere the command is written into code (CI, package.json, scripts), use `turbo run`.
-
-### Enable Remote Caching
-
-Remote caching dramatically speeds up CI by sharing cached artifacts across runs.
-
-Required environment variables:
-
-```bash
-TURBO_TOKEN=your_vercel_token
-TURBO_TEAM=your_team_slug
-```
-
-### Use --affected for PR Builds
-
-The `--affected` flag only runs tasks for packages changed since the base branch:
-
-```bash
-turbo run build test --affected
-```
-
-This requires Git history to compute what changed.
-
-## Git History Requirements
-
-### Fetch Depth
-
-`--affected` needs access to the merge base. Shallow clones break this.
-
-```yaml
-# GitHub Actions
-- uses: actions/checkout@v4
-  with:
-    fetch-depth: 2 # Minimum for --affected
-    # Use 0 for full history if merge base is far
-```
-
-### Why Shallow Clones Break --affected
-
-Turborepo compares the current HEAD to the merge base with `main`. If that commit isn't fetched, `--affected` falls back to running everything.
-
-For PRs with many commits, consider:
-
-```yaml
-fetch-depth: 0 # Full history
-```
-
-## Environment Variables Reference
-
-| Variable            | Purpose                              |
-| ------------------- | ------------------------------------ |
-| `TURBO_TOKEN`       | Vercel access token for remote cache |
-| `TURBO_TEAM`        | Your Vercel team slug                |
-| `TURBO_REMOTE_ONLY` | Skip local cache, use remote only    |
-| `TURBO_LOG_ORDER`   | Set to `grouped` for cleaner CI logs |
-
-## See Also
-
-- [github-actions.md](./github-actions.md) - GitHub Actions setup
-- [vercel.md](./vercel.md) - Vercel deployment
-- [patterns.md](./patterns.md) - CI optimization patterns

+ 0 - 162
neo/.agents/skills/turborepo/references/ci/github-actions.md

@@ -1,162 +0,0 @@
-# GitHub Actions
-
-Complete setup guide for Turborepo with GitHub Actions.
-
-## Basic Workflow Structure
-
-```yaml
-name: CI
-
-on:
-  push:
-    branches: [main]
-  pull_request:
-    branches: [main]
-
-jobs:
-  build:
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v4
-        with:
-          fetch-depth: 2
-
-      - uses: actions/setup-node@v4
-        with:
-          node-version: 20
-
-      - name: Install dependencies
-        run: npm ci
-
-      - name: Build and Test
-        run: turbo run build test lint
-```
-
-## Package Manager Setup
-
-### pnpm
-
-```yaml
-- uses: pnpm/action-setup@v3
-  with:
-    version: 9
-
-- uses: actions/setup-node@v4
-  with:
-    node-version: 20
-    cache: "pnpm"
-
-- run: pnpm install --frozen-lockfile
-```
-
-### Yarn
-
-```yaml
-- uses: actions/setup-node@v4
-  with:
-    node-version: 20
-    cache: "yarn"
-
-- run: yarn install --frozen-lockfile
-```
-
-### Bun
-
-```yaml
-- uses: oven-sh/setup-bun@v1
-  with:
-    bun-version: latest
-
-- run: bun install --frozen-lockfile
-```
-
-## Remote Cache Setup
-
-### 1. Create Vercel Access Token
-
-1. Go to [Vercel Dashboard](https://vercel.com/account/tokens)
-2. Create a new token with appropriate scope
-3. Copy the token value
-
-### 2. Add Secrets and Variables
-
-In your GitHub repository settings:
-
-**Secrets** (Settings > Secrets and variables > Actions > Secrets):
-
-- `TURBO_TOKEN`: Your Vercel access token
-
-**Variables** (Settings > Secrets and variables > Actions > Variables):
-
-- `TURBO_TEAM`: Your Vercel team slug
-
-### 3. Add to Workflow
-
-```yaml
-jobs:
-  build:
-    runs-on: ubuntu-latest
-    env:
-      TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
-      TURBO_TEAM: ${{ vars.TURBO_TEAM }}
-```
-
-## Alternative: actions/cache
-
-If you can't use remote cache, cache Turborepo's local cache directory:
-
-```yaml
-- uses: actions/cache@v4
-  with:
-    path: .turbo
-    key: turbo-${{ runner.os }}-${{ hashFiles('**/turbo.json', '**/package-lock.json') }}
-    restore-keys: |
-      turbo-${{ runner.os }}-
-```
-
-Note: This is less effective than remote cache since it's per-branch.
-
-## Complete Example
-
-```yaml
-name: CI
-
-on:
-  push:
-    branches: [main]
-  pull_request:
-    branches: [main]
-
-jobs:
-  build:
-    runs-on: ubuntu-latest
-    env:
-      TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
-      TURBO_TEAM: ${{ vars.TURBO_TEAM }}
-
-    steps:
-      - uses: actions/checkout@v4
-        with:
-          fetch-depth: 2
-
-      - uses: pnpm/action-setup@v3
-        with:
-          version: 9
-
-      - uses: actions/setup-node@v4
-        with:
-          node-version: 20
-          cache: "pnpm"
-
-      - name: Install dependencies
-        run: pnpm install --frozen-lockfile
-
-      - name: Build
-        run: turbo run build --affected
-
-      - name: Test
-        run: turbo run test --affected
-
-      - name: Lint
-        run: turbo run lint --affected
-```

+ 0 - 145
neo/.agents/skills/turborepo/references/ci/patterns.md

@@ -1,145 +0,0 @@
-# CI Optimization Patterns
-
-Strategies for efficient CI/CD with Turborepo.
-
-## PR vs Main Branch Builds
-
-### PR Builds: Only Affected
-
-Test only what changed in the PR:
-
-```yaml
-- name: Test (PR)
-  if: github.event_name == 'pull_request'
-  run: turbo run build test --affected
-```
-
-### Main Branch: Full Build
-
-Ensure complete validation on merge:
-
-```yaml
-- name: Test (Main)
-  if: github.ref == 'refs/heads/main'
-  run: turbo run build test
-```
-
-## Custom Git Ranges with --filter
-
-For advanced scenarios, use `--filter` with git refs:
-
-```bash
-# Changes since specific commit
-turbo run test --filter="...[abc123]"
-
-# Changes between refs
-turbo run test --filter="...[main...HEAD]"
-
-# Changes in last 3 commits
-turbo run test --filter="...[HEAD~3]"
-```
-
-## Caching Strategies
-
-### Remote Cache (Recommended)
-
-Best performance - shared across all CI runs and developers:
-
-```yaml
-env:
-  TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
-  TURBO_TEAM: ${{ vars.TURBO_TEAM }}
-```
-
-### actions/cache Fallback
-
-When remote cache isn't available:
-
-```yaml
-- uses: actions/cache@v4
-  with:
-    path: .turbo
-    key: turbo-${{ runner.os }}-${{ github.sha }}
-    restore-keys: |
-      turbo-${{ runner.os }}-${{ github.ref }}-
-      turbo-${{ runner.os }}-
-```
-
-Limitations:
-
-- Cache is branch-scoped
-- PRs restore from base branch cache
-- Less efficient than remote cache
-
-## Matrix Builds
-
-Test across Node versions:
-
-```yaml
-strategy:
-  matrix:
-    node: [18, 20, 22]
-
-steps:
-  - uses: actions/setup-node@v4
-    with:
-      node-version: ${{ matrix.node }}
-
-  - run: turbo run test
-```
-
-## Parallelizing Across Jobs
-
-Split tasks into separate jobs:
-
-```yaml
-jobs:
-  lint:
-    runs-on: ubuntu-latest
-    steps:
-      - run: turbo run lint --affected
-
-  test:
-    runs-on: ubuntu-latest
-    steps:
-      - run: turbo run test --affected
-
-  build:
-    runs-on: ubuntu-latest
-    needs: [lint, test]
-    steps:
-      - run: turbo run build
-```
-
-### Cache Considerations
-
-When parallelizing:
-
-- Each job has separate cache writes
-- Remote cache handles this automatically
-- With actions/cache, use unique keys per job to avoid conflicts
-
-```yaml
-- uses: actions/cache@v4
-  with:
-    path: .turbo
-    key: turbo-${{ runner.os }}-${{ github.job }}-${{ github.sha }}
-```
-
-## Conditional Tasks
-
-Skip expensive tasks on draft PRs:
-
-```yaml
-- name: E2E Tests
-  if: github.event.pull_request.draft == false
-  run: turbo run test:e2e --affected
-```
-
-Or require label for full test:
-
-```yaml
-- name: Full Test Suite
-  if: contains(github.event.pull_request.labels.*.name, 'full-test')
-  run: turbo run test
-```

+ 0 - 103
neo/.agents/skills/turborepo/references/ci/vercel.md

@@ -1,103 +0,0 @@
-# Vercel Deployment
-
-Turborepo integrates seamlessly with Vercel for monorepo deployments.
-
-## Remote Cache
-
-Remote caching is **automatically enabled** when deploying to Vercel. No configuration needed - Vercel detects Turborepo and enables caching.
-
-This means:
-
-- No `TURBO_TOKEN` or `TURBO_TEAM` setup required on Vercel
-- Cache is shared across all deployments
-- Preview and production builds benefit from cache
-
-## turbo-ignore
-
-Skip unnecessary builds when a package hasn't changed using `turbo-ignore`.
-
-### Installation
-
-```bash
-npx turbo-ignore
-```
-
-Or install globally in your project:
-
-```bash
-pnpm add -D turbo-ignore
-```
-
-### Setup in Vercel
-
-1. Go to your project in Vercel Dashboard
-2. Navigate to Settings > Git > Ignored Build Step
-3. Select "Custom" and enter:
-
-```bash
-npx turbo-ignore
-```
-
-### How It Works
-
-`turbo-ignore` checks if the current package (or its dependencies) changed since the last successful deployment:
-
-1. Compares current commit to last deployed commit
-2. Uses Turborepo's dependency graph
-3. Returns exit code 0 (skip) if no changes
-4. Returns exit code 1 (build) if changes detected
-
-### Options
-
-```bash
-# Check specific package
-npx turbo-ignore web
-
-# Use specific comparison ref
-npx turbo-ignore --fallback=HEAD~1
-
-# Verbose output
-npx turbo-ignore --verbose
-```
-
-## Environment Variables
-
-Set environment variables in Vercel Dashboard:
-
-1. Go to Project Settings > Environment Variables
-2. Add variables for each environment (Production, Preview, Development)
-
-Common variables:
-
-- `DATABASE_URL`
-- `API_KEY`
-- Package-specific config
-
-## Monorepo Root Directory
-
-For monorepos, set the root directory in Vercel:
-
-1. Project Settings > General > Root Directory
-2. Set to the package path (e.g., `apps/web`)
-
-Vercel automatically:
-
-- Installs dependencies from monorepo root
-- Runs build from the package directory
-- Detects framework settings
-
-## Build Command
-
-Vercel auto-detects `turbo run build` when `turbo.json` exists at root.
-
-Override if needed:
-
-```bash
-turbo run build --filter=web
-```
-
-Or for production-only optimizations:
-
-```bash
-turbo run build --filter=web --env-mode=strict
-```

+ 0 - 100
neo/.agents/skills/turborepo/references/cli/RULE.md

@@ -1,100 +0,0 @@
-# turbo run
-
-The primary command for executing tasks across your monorepo.
-
-## Basic Usage
-
-```bash
-# Full form (use in CI, package.json, scripts)
-turbo run <tasks>
-
-# Shorthand (only for one-off terminal invocations)
-turbo <tasks>
-```
-
-## When to Use `turbo run` vs `turbo`
-
-**Always use `turbo run` when the command is written into code:**
-
-- `package.json` scripts
-- CI/CD workflows (GitHub Actions, etc.)
-- Shell scripts
-- Documentation
-- Any static/committed configuration
-
-**Only use `turbo` (shorthand) for:**
-
-- One-off commands typed directly in terminal
-- Ad-hoc invocations by humans or agents
-
-```json
-// package.json - ALWAYS use "turbo run"
-{
-  "scripts": {
-    "build": "turbo run build",
-    "dev": "turbo run dev",
-    "lint": "turbo run lint",
-    "test": "turbo run test"
-  }
-}
-```
-
-```yaml
-# CI workflow - ALWAYS use "turbo run"
-- run: turbo run build --affected
-- run: turbo run test --affected
-```
-
-```bash
-# Terminal one-off - shorthand OK
-turbo build --filter=web
-```
-
-## Running Tasks
-
-Tasks must be defined in `turbo.json` before running.
-
-```bash
-# Single task
-turbo build
-
-# Multiple tasks
-turbo run build lint test
-
-# See available tasks (run without arguments)
-turbo run
-```
-
-## Passing Arguments to Scripts
-
-Use `--` to pass arguments through to the underlying package scripts:
-
-```bash
-turbo run build -- --sourcemap
-turbo test -- --watch
-turbo lint -- --fix
-```
-
-Everything after `--` goes directly to the task's script.
-
-## Package Selection
-
-By default, turbo runs tasks in all packages. Use `--filter` to narrow scope:
-
-```bash
-turbo build --filter=web
-turbo test --filter=./apps/*
-```
-
-See `filtering/` for complete filter syntax.
-
-## Quick Reference
-
-| Goal                | Command                    |
-| ------------------- | -------------------------- |
-| Build everything    | `turbo build`              |
-| Build one package   | `turbo build --filter=web` |
-| Multiple tasks      | `turbo build lint test`    |
-| Pass args to script | `turbo build -- --arg`     |
-| Preview run         | `turbo build --dry`        |
-| Force rebuild       | `turbo build --force`      |

+ 0 - 297
neo/.agents/skills/turborepo/references/cli/commands.md

@@ -1,297 +0,0 @@
-# turbo run Flags Reference
-
-Full docs: https://turborepo.dev/docs/reference/run
-
-## Package Selection
-
-### `--filter` / `-F`
-
-Select specific packages to run tasks in.
-
-```bash
-turbo build --filter=web
-turbo build -F=@repo/ui -F=@repo/utils
-turbo test --filter=./apps/*
-```
-
-See `filtering/` for complete syntax (globs, dependencies, git ranges).
-
-### Task Identifier Syntax (v2.2.4+)
-
-Run specific package tasks directly:
-
-```bash
-turbo run web#build              # Build web package
-turbo run web#build docs#lint    # Multiple specific tasks
-```
-
-### `--affected`
-
-Run only in packages changed since the base branch.
-
-```bash
-turbo build --affected
-turbo test --affected --filter=./apps/*  # combine with filter
-```
-
-**How it works:**
-
-- Default: compares `main...HEAD`
-- In GitHub Actions: auto-detects `GITHUB_BASE_REF`
-- Override base: `TURBO_SCM_BASE=development turbo build --affected`
-- Override head: `TURBO_SCM_HEAD=your-branch turbo build --affected`
-
-**Requires git history** - shallow clones may fall back to running all tasks.
-
-## Execution Control
-
-### `--dry` / `--dry=json`
-
-Preview what would run without executing.
-
-```bash
-turbo build --dry          # human-readable
-turbo build --dry=json     # machine-readable
-```
-
-### `--force`
-
-Ignore all cached artifacts, re-run everything.
-
-```bash
-turbo build --force
-```
-
-### `--concurrency`
-
-Limit parallel task execution.
-
-```bash
-turbo build --concurrency=4      # max 4 tasks
-turbo build --concurrency=50%    # 50% of CPU cores
-```
-
-### `--continue`
-
-Keep running other tasks when one fails.
-
-```bash
-turbo build test --continue
-```
-
-### `--only`
-
-Run only the specified task, skip its dependencies.
-
-```bash
-turbo build --only  # skip running dependsOn tasks
-```
-
-### `--parallel` (Discouraged)
-
-Ignores task graph dependencies, runs all tasks simultaneously. **Avoid using this flag**—if tasks need to run in parallel, configure `dependsOn` correctly instead. Using `--parallel` bypasses Turborepo's dependency graph, which can cause race conditions and incorrect builds.
-
-## Cache Control
-
-### `--cache`
-
-Fine-grained cache behavior control.
-
-```bash
-# Default: read/write both local and remote
-turbo build --cache=local:rw,remote:rw
-
-# Read-only local, no remote
-turbo build --cache=local:r,remote:
-
-# Disable local, read-only remote
-turbo build --cache=local:,remote:r
-
-# Disable all caching
-turbo build --cache=local:,remote:
-```
-
-## Output & Debugging
-
-### `--graph`
-
-Generate task graph visualization.
-
-```bash
-turbo build --graph                # opens in browser
-turbo build --graph=graph.svg      # SVG file
-turbo build --graph=graph.png      # PNG file
-turbo build --graph=graph.json     # JSON data
-turbo build --graph=graph.mermaid  # Mermaid diagram
-```
-
-### `--summarize`
-
-Generate JSON run summary for debugging.
-
-```bash
-turbo build --summarize
-# creates .turbo/runs/<run-id>.json
-```
-
-### `--output-logs`
-
-Control log output verbosity.
-
-```bash
-turbo build --output-logs=full        # all logs (default)
-turbo build --output-logs=new-only    # only cache misses
-turbo build --output-logs=errors-only # only failures
-turbo build --output-logs=none        # silent
-```
-
-### `--profile`
-
-Generate Chrome tracing profile for performance analysis.
-
-```bash
-turbo build --profile=profile.json
-# open chrome://tracing and load the file
-```
-
-### `--verbosity` / `-v`
-
-Control turbo's own log level.
-
-```bash
-turbo build -v      # verbose
-turbo build -vv     # more verbose
-turbo build -vvv    # maximum verbosity
-```
-
-## Environment
-
-### `--env-mode`
-
-Control environment variable handling.
-
-```bash
-turbo build --env-mode=strict  # only declared env vars (default)
-turbo build --env-mode=loose   # include all env vars in hash
-```
-
-## UI
-
-### `--ui`
-
-Select output interface.
-
-```bash
-turbo build --ui=tui     # interactive terminal UI (default in TTY)
-turbo build --ui=stream  # streaming logs (default in CI)
-```
-
----
-
-# turbo-ignore
-
-Full docs: https://turborepo.dev/docs/reference/turbo-ignore
-
-Skip CI work when nothing relevant changed. Useful for skipping container setup.
-
-## Basic Usage
-
-```bash
-# Check if build is needed for current package (uses Automatic Package Scoping)
-npx turbo-ignore
-
-# Check specific package
-npx turbo-ignore web
-
-# Check specific task
-npx turbo-ignore --task=test
-```
-
-## Exit Codes
-
-- `0`: No changes detected - skip CI work
-- `1`: Changes detected - proceed with CI
-
-## CI Integration Example
-
-```yaml
-# GitHub Actions
-- name: Check for changes
-  id: turbo-ignore
-  run: npx turbo-ignore web
-  continue-on-error: true
-
-- name: Build
-  if: steps.turbo-ignore.outcome == 'failure'  # changes detected
-  run: pnpm build
-```
-
-## Comparison Depth
-
-Default: compares to parent commit (`HEAD^1`).
-
-```bash
-# Compare to specific commit
-npx turbo-ignore --fallback=abc123
-
-# Compare to branch
-npx turbo-ignore --fallback=main
-```
-
----
-
-# Other Commands
-
-## turbo boundaries
-
-Check workspace violations (experimental).
-
-```bash
-turbo boundaries
-```
-
-See `references/boundaries/` for configuration.
-
-## turbo watch
-
-Re-run tasks on file changes.
-
-```bash
-turbo watch build test
-```
-
-See `references/watch/` for details.
-
-## turbo prune
-
-Create sparse checkout for Docker.
-
-```bash
-turbo prune web --docker
-```
-
-## turbo link / unlink
-
-Connect/disconnect Remote Cache.
-
-```bash
-turbo link    # connect to Vercel Remote Cache
-turbo unlink  # disconnect
-```
-
-## turbo login / logout
-
-Authenticate with Remote Cache provider.
-
-```bash
-turbo login   # authenticate
-turbo logout  # log out
-```
-
-## turbo generate
-
-Scaffold new packages.
-
-```bash
-turbo generate
-```

+ 0 - 235
neo/.agents/skills/turborepo/references/configuration/RULE.md

@@ -1,235 +0,0 @@
-# turbo.json Configuration Overview
-
-Configuration reference for Turborepo. Full docs: https://turborepo.dev/docs/reference/configuration
-
-## File Location
-
-Root `turbo.json` lives at repo root, sibling to root `package.json`:
-
-```
-my-monorepo/
-├── turbo.json        # Root configuration
-├── package.json
-└── packages/
-    └── web/
-        ├── turbo.json  # Package Configuration (optional)
-        └── package.json
-```
-
-## Always Prefer Package Tasks Over Root Tasks
-
-**Always use package tasks. Only use Root Tasks if you cannot succeed with package tasks.**
-
-Package tasks enable parallelization, individual caching, and filtering. Define scripts in each package's `package.json`:
-
-```json
-// packages/web/package.json
-{
-  "scripts": {
-    "build": "next build",
-    "lint": "eslint .",
-    "test": "vitest",
-    "typecheck": "tsc --noEmit"
-  }
-}
-
-// packages/api/package.json
-{
-  "scripts": {
-    "build": "tsc",
-    "lint": "eslint .",
-    "test": "vitest",
-    "typecheck": "tsc --noEmit"
-  }
-}
-```
-
-```json
-// Root package.json - delegates to turbo
-{
-  "scripts": {
-    "build": "turbo run build",
-    "lint": "turbo run lint",
-    "test": "turbo run test",
-    "typecheck": "turbo run typecheck"
-  }
-}
-```
-
-When you run `turbo run lint`, Turborepo finds all packages with a `lint` script and runs them **in parallel**.
-
-**Root Tasks are a fallback**, not the default. Only use them for tasks that truly cannot run per-package (e.g., repo-level CI scripts, workspace-wide config generation).
-
-```json
-// AVOID: Task logic in root defeats parallelization
-{
-  "scripts": {
-    "lint": "eslint apps/web && eslint apps/api && eslint packages/ui"
-  }
-}
-```
-
-## Basic Structure
-
-```json
-{
-  "$schema": "https://v2-8-21-canary-9.turborepo.dev/schema.json",
-  "globalEnv": ["CI"],
-  "globalDependencies": ["tsconfig.json"],
-  "tasks": {
-    "build": {
-      "dependsOn": ["^build"],
-      "outputs": ["dist/**"]
-    },
-    "dev": {
-      "cache": false,
-      "persistent": true
-    }
-  }
-}
-```
-
-The `$schema` key enables IDE autocompletion and validation.
-
-### With `futureFlags.globalConfiguration`
-
-When the `globalConfiguration` future flag is enabled, global options move under a `global` key with cleaner names:
-
-```json
-{
-  "$schema": "https://v2-8-21-canary-9.turborepo.dev/schema.json",
-  "futureFlags": { "globalConfiguration": true },
-  "global": {
-    "inputs": ["tsconfig.json"],
-    "env": ["CI"],
-    "ui": "tui"
-  },
-  "tasks": {
-    "build": {
-      "dependsOn": ["^build"],
-      "outputs": ["dist/**"]
-    }
-  }
-}
-```
-
-See the [global options reference](./global-options.md) for the full rename mapping and behavior changes.
-
-## Configuration Sections
-
-**Global options** - Settings affecting all tasks:
-
-- Without flag: `globalEnv`, `globalDependencies`, `globalPassThroughEnv`, `cacheDir`, `daemon`, `envMode`, `ui`, `remoteCache`
-- With `globalConfiguration` flag: all of the above move under the `global` key (see [global options](./global-options.md))
-
-**Task definitions** - Per-task settings in `tasks` object:
-
-- `dependsOn`, `outputs`, `inputs`, `env`
-- `cache`, `persistent`, `interactive`, `outputLogs`
-
-## Package Configurations
-
-Use `turbo.json` in individual packages to override root settings:
-
-```json
-// packages/web/turbo.json
-{
-  "extends": ["//"],
-  "tasks": {
-    "build": {
-      "outputs": [".next/**", "!.next/cache/**"]
-    }
-  }
-}
-```
-
-The `"extends": ["//"]` is required - it references the root configuration.
-
-**When to use Package Configurations:**
-
-- Framework-specific outputs (Next.js, Vite, etc.)
-- Package-specific env vars
-- Different caching rules for specific packages
-- Keeping framework config close to the framework code
-
-### Extending from Other Packages
-
-You can extend from config packages instead of just root:
-
-```json
-// packages/web/turbo.json
-{
-  "extends": ["//", "@repo/turbo-config"]
-}
-```
-
-### Adding to Inherited Arrays with `$TURBO_EXTENDS$`
-
-By default, array fields in Package Configurations **replace** root values. Use `$TURBO_EXTENDS$` to **append** instead:
-
-```json
-// Root turbo.json
-{
-  "tasks": {
-    "build": {
-      "outputs": ["dist/**"]
-    }
-  }
-}
-```
-
-```json
-// packages/web/turbo.json
-{
-  "extends": ["//"],
-  "tasks": {
-    "build": {
-      // Inherits "dist/**" from root, adds ".next/**"
-      "outputs": ["$TURBO_EXTENDS$", ".next/**", "!.next/cache/**"]
-    }
-  }
-}
-```
-
-Without `$TURBO_EXTENDS$`, outputs would only be `[".next/**", "!.next/cache/**"]`.
-
-**Works with:**
-
-- `dependsOn`
-- `env`
-- `inputs`
-- `outputs`
-- `passThroughEnv`
-- `with`
-
-### Excluding Tasks from Packages
-
-Use `extends: false` to exclude a task from a package:
-
-```json
-// packages/ui/turbo.json
-{
-  "extends": ["//"],
-  "tasks": {
-    "e2e": {
-      "extends": false // UI package doesn't have e2e tests
-    }
-  }
-}
-```
-
-## `turbo.jsonc` for Comments
-
-Use `turbo.jsonc` extension to add comments with IDE support:
-
-```jsonc
-// turbo.jsonc
-{
-  "tasks": {
-    "build": {
-      // Next.js outputs
-      "outputs": [".next/**", "!.next/cache/**"]
-    }
-  }
-}
-```

+ 0 - 239
neo/.agents/skills/turborepo/references/configuration/global-options.md

@@ -1,239 +0,0 @@
-# Global Options Reference
-
-Options that affect all tasks. Full docs: https://turborepo.dev/docs/reference/configuration
-
-## globalEnv
-
-Environment variables affecting all task hashes.
-
-```json
-{
-  "globalEnv": ["CI", "NODE_ENV", "VERCEL_*"]
-}
-```
-
-Use for variables that should invalidate all caches when changed.
-
-## globalDependencies
-
-Files that affect all task hashes.
-
-```json
-{
-  "globalDependencies": ["tsconfig.json", ".env", "pnpm-lock.yaml"]
-}
-```
-
-Lockfile is included by default. Add shared configs here.
-
-## globalPassThroughEnv
-
-Variables available to tasks but not included in hash.
-
-```json
-{
-  "globalPassThroughEnv": ["AWS_SECRET_KEY", "GITHUB_TOKEN"]
-}
-```
-
-Use for credentials that shouldn't affect cache keys.
-
-## cacheDir
-
-Custom cache location. Default: `node_modules/.cache/turbo`.
-
-```json
-{
-  "cacheDir": ".turbo/cache"
-}
-```
-
-## daemon
-
-**Deprecated**: The daemon is no longer used for `turbo run` and this option will be removed in version 3.0. The daemon is still used by `turbo watch` and the Turborepo LSP.
-
-## envMode
-
-How unspecified env vars are handled. Default: `"strict"`.
-
-```json
-{
-  "envMode": "strict"  // Only specified vars available
-  // or
-  "envMode": "loose"   // All vars pass through
-}
-```
-
-Strict mode catches missing env declarations.
-
-## ui
-
-Terminal UI mode. Default: `"stream"`.
-
-```json
-{
-  "ui": "tui"     // Interactive terminal UI
-  // or
-  "ui": "stream"  // Traditional streaming logs
-}
-```
-
-TUI provides better UX for parallel tasks.
-
-## remoteCache
-
-Configure remote caching.
-
-```json
-{
-  "remoteCache": {
-    "enabled": true,
-    "signature": true,
-    "timeout": 30,
-    "uploadTimeout": 60
-  }
-}
-```
-
-| Option          | Default                | Description                                            |
-| --------------- | ---------------------- | ------------------------------------------------------ |
-| `enabled`       | `true`                 | Enable/disable remote caching                          |
-| `signature`     | `false`                | Sign artifacts with `TURBO_REMOTE_CACHE_SIGNATURE_KEY` |
-| `preflight`     | `false`                | Send OPTIONS request before cache requests             |
-| `timeout`       | `30`                   | Timeout in seconds for cache operations                |
-| `uploadTimeout` | `60`                   | Timeout in seconds for uploads                         |
-| `apiUrl`        | `"https://vercel.com"` | Remote cache API endpoint                              |
-| `loginUrl`      | `"https://vercel.com"` | Login endpoint                                         |
-| `teamId`        | -                      | Team ID (must start with `team_`)                      |
-| `teamSlug`      | -                      | Team slug for querystring                              |
-
-See https://turborepo.dev/docs/core-concepts/remote-caching for setup.
-
-## concurrency
-
-Default: `"10"`
-
-Limit parallel task execution.
-
-```json
-{
-  "concurrency": "4"     // Max 4 tasks at once
-  // or
-  "concurrency": "50%"   // 50% of available CPUs
-}
-```
-
-## futureFlags
-
-Enable experimental features that will become default in future versions.
-
-```json
-{
-  "futureFlags": {
-    "errorsOnlyShowHash": true
-  }
-}
-```
-
-### `errorsOnlyShowHash`
-
-When using `outputLogs: "errors-only"`, show task hashes on start/completion:
-
-- Cache miss: `cache miss, executing <hash> (only logging errors)`
-- Cache hit: `cache hit, replaying logs (no errors) <hash>`
-
-### `longerSignatureKey`
-
-Enforce a minimum key length of 32 bytes for `TURBO_REMOTE_CACHE_SIGNATURE_KEY` when `remoteCache.signature` is enabled. Short keys weaken HMAC-SHA256 signatures. Fails the run immediately if the key is too short.
-
-### `globalConfiguration`
-
-Moves global configuration keys under a top-level `global` key for clarity and changes how `global.inputs` (formerly `globalDependencies`) affects task hashing.
-
-When enabled:
-
-- Global config keys move under `global` with cleaner names
-- `global.inputs` files are **prepended to every task's inputs** instead of being folded into the global hash — tasks can opt out of specific global inputs using negation globs
-
-```json
-{
-  "futureFlags": { "globalConfiguration": true },
-  "global": {
-    "inputs": ["tsconfig.json", ".env"],
-    "env": ["CI", "NODE_ENV"],
-    "passThroughEnv": ["AWS_SECRET_KEY"],
-    "ui": "tui",
-    "envMode": "strict",
-    "cacheDir": ".turbo/cache",
-    "remoteCache": { "enabled": true },
-    "concurrency": "50%"
-  },
-  "tasks": {
-    "build": {
-      "dependsOn": ["^build"],
-      "outputs": ["dist/**"]
-    }
-  }
-}
-```
-
-**Key rename mapping:**
-
-| Old (top-level)                                                                                                                  | New (`global.`)           |
-| -------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
-| `globalDependencies`                                                                                                             | `inputs`                  |
-| `globalEnv`                                                                                                                      | `env`                     |
-| `globalPassThroughEnv`                                                                                                           | `passThroughEnv`          |
-| `ui`, `envMode`, `cacheDir`, `daemon`, `concurrency`, `noUpdateNotifier`, `dangerouslyDisablePackageManagerCheck`, `remoteCache` | Same names under `global` |
-
-**Behavior change for `global.inputs`:**
-
-With `globalDependencies` (old): files are hashed into the **global hash**, which is embedded in every task's cache key. Changing any of these files invalidates all tasks — there is no opt-out.
-
-With `global.inputs` (new): files are treated as **implicit task inputs** prepended to each task's `inputs` globs. This means:
-
-- Tasks can exclude specific global files: `"inputs": ["$TURBO_DEFAULT$", "!$TURBO_ROOT$/tsconfig.json"]`
-- The global hash no longer includes these file hashes (it still includes lockfile, engines, global env, etc.)
-- Tasks with no explicit `inputs` still hash all package files plus the global inputs
-
-See the [gotchas doc](./gotchas.md) for guidance on using `$TURBO_DEFAULT$` with `global.inputs`.
-
-## noUpdateNotifier
-
-Disable update notifications when new turbo versions are available.
-
-```json
-{
-  "noUpdateNotifier": true
-}
-```
-
-## dangerouslyDisablePackageManagerCheck
-
-Bypass the `packageManager` field requirement. Use for incremental migration.
-
-```json
-{
-  "dangerouslyDisablePackageManagerCheck": true
-}
-```
-
-**Warning**: Unstable lockfiles can cause unpredictable behavior.
-
-## Git Worktree Cache Sharing
-
-When working in Git worktrees, Turborepo automatically shares local cache between the main worktree and linked worktrees.
-
-**How it works:**
-
-- Detects worktree configuration
-- Redirects cache to main worktree's `.turbo/cache`
-- Works alongside Remote Cache
-
-**Benefits:**
-
-- Cache hits across branches
-- Reduced disk usage
-- Faster branch switching
-
-**Disabled by**: Setting explicit `cacheDir` in turbo.json.

+ 0 - 368
neo/.agents/skills/turborepo/references/configuration/gotchas.md

@@ -1,368 +0,0 @@
-# Configuration Gotchas
-
-Common mistakes and how to fix them.
-
-## #1 Root Scripts Not Using `turbo run`
-
-Root `package.json` scripts for turbo tasks MUST use `turbo run`, not direct commands.
-
-```json
-// WRONG - bypasses turbo, no parallelization or caching
-{
-  "scripts": {
-    "build": "bun build",
-    "dev": "bun dev"
-  }
-}
-
-// CORRECT - delegates to turbo
-{
-  "scripts": {
-    "build": "turbo run build",
-    "dev": "turbo run dev"
-  }
-}
-```
-
-**Why this matters:** Running `bun build` or `npm run build` at root bypasses Turborepo entirely - no parallelization, no caching, no dependency graph awareness.
-
-## #2 Using `&&` to Chain Turbo Tasks
-
-Don't use `&&` to chain tasks that turbo should orchestrate.
-
-```json
-// WRONG - changeset:publish chains turbo task with non-turbo command
-{
-  "scripts": {
-    "changeset:publish": "bun build && changeset publish"
-  }
-}
-
-// CORRECT - use turbo run, let turbo handle dependencies
-{
-  "scripts": {
-    "changeset:publish": "turbo run build && changeset publish"
-  }
-}
-```
-
-If the second command (`changeset publish`) depends on build outputs, the turbo task should run through turbo to get caching and parallelization benefits.
-
-## #3 Overly Broad globalDependencies
-
-`globalDependencies` affects hash for ALL tasks in ALL packages. Be specific.
-
-```json
-// WRONG - affects all hashes
-{
-  "globalDependencies": ["**/.env.*local"]
-}
-
-// CORRECT - move to specific tasks that need it
-{
-  "globalDependencies": [".env"],
-  "tasks": {
-    "build": {
-      "inputs": ["$TURBO_DEFAULT$", ".env*"],
-      "outputs": ["dist/**"]
-    }
-  }
-}
-```
-
-**Why this matters:** `**/.env.*local` matches .env files in ALL packages, causing unnecessary cache invalidation. Instead:
-
-- Use `globalDependencies` only for truly global files (root `.env`)
-- Use task-level `inputs` for package-specific .env files with `$TURBO_DEFAULT$` to preserve default behavior
-
-With `futureFlags.globalConfiguration`, this is less of a concern because `global.inputs` acts as implicit task inputs — tasks can opt out of specific files with negation globs. But keeping the list focused is still good practice.
-
-## #4 Repetitive Task Configuration
-
-Look for repeated configuration across tasks that can be collapsed.
-
-```json
-// WRONG - repetitive env and inputs across tasks
-{
-  "tasks": {
-    "build": {
-      "env": ["API_URL", "DATABASE_URL"],
-      "inputs": ["$TURBO_DEFAULT$", ".env*"]
-    },
-    "test": {
-      "env": ["API_URL", "DATABASE_URL"],
-      "inputs": ["$TURBO_DEFAULT$", ".env*"]
-    }
-  }
-}
-
-// BETTER - use globalEnv and globalDependencies
-{
-  "globalEnv": ["API_URL", "DATABASE_URL"],
-  "globalDependencies": [".env*"],
-  "tasks": {
-    "build": {},
-    "test": {}
-  }
-}
-```
-
-**When to use global vs task-level:**
-
-- `globalEnv` / `globalDependencies` - affects ALL tasks, use for truly shared config
-- Task-level `env` / `inputs` - use when only specific tasks need it
-
-## #5 Using `../` to Traverse Out of Package in `inputs`
-
-Don't use relative paths like `../` to reference files outside the package. Use `$TURBO_ROOT$` instead.
-
-```json
-// WRONG - traversing out of package
-{
-  "tasks": {
-    "build": {
-      "inputs": ["$TURBO_DEFAULT$", "../shared-config.json"]
-    }
-  }
-}
-
-// CORRECT - use $TURBO_ROOT$ for repo root
-{
-  "tasks": {
-    "build": {
-      "inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/shared-config.json"]
-    }
-  }
-}
-```
-
-## #6 MOST COMMON MISTAKE: Creating Root Tasks
-
-**DO NOT create Root Tasks. ALWAYS create package tasks.**
-
-When you need to create a task (build, lint, test, typecheck, etc.):
-
-1. Add the script to **each relevant package's** `package.json`
-2. Register the task in root `turbo.json`
-3. Root `package.json` only contains `turbo run <task>`
-
-```json
-// WRONG - DO NOT DO THIS
-// Root package.json with task logic
-{
-  "scripts": {
-    "build": "cd apps/web && next build && cd ../api && tsc",
-    "lint": "eslint apps/ packages/",
-    "test": "vitest"
-  }
-}
-
-// CORRECT - DO THIS
-// apps/web/package.json
-{ "scripts": { "build": "next build", "lint": "eslint .", "test": "vitest" } }
-
-// apps/api/package.json
-{ "scripts": { "build": "tsc", "lint": "eslint .", "test": "vitest" } }
-
-// packages/ui/package.json
-{ "scripts": { "build": "tsc", "lint": "eslint .", "test": "vitest" } }
-
-// Root package.json - ONLY delegates
-{ "scripts": { "build": "turbo run build", "lint": "turbo run lint", "test": "turbo run test" } }
-
-// turbo.json - register tasks
-{
-  "tasks": {
-    "build": { "dependsOn": ["^build"], "outputs": ["dist/**"] },
-    "lint": {},
-    "test": {}
-  }
-}
-```
-
-**Why this matters:**
-
-- Package tasks run in **parallel** across all packages
-- Each package's output is cached **individually**
-- You can **filter** to specific packages: `turbo run test --filter=web`
-
-Root Tasks (`//#taskname`) defeat all these benefits. Only use them for tasks that truly cannot exist in any package (extremely rare).
-
-## #7 Tasks That Need Parallel Execution + Cache Invalidation
-
-Some tasks can run in parallel (don't need built output from dependencies) but must still invalidate cache when dependency source code changes. Using `dependsOn: ["^taskname"]` forces sequential execution. Using no dependencies breaks cache invalidation.
-
-**Use Transit Nodes for these tasks:**
-
-```json
-// WRONG - forces sequential execution (SLOW)
-"my-task": {
-  "dependsOn": ["^my-task"]
-}
-
-// ALSO WRONG - no dependency awareness (INCORRECT CACHING)
-"my-task": {}
-
-// CORRECT - use Transit Nodes for parallel + correct caching
-{
-  "tasks": {
-    "transit": { "dependsOn": ["^transit"] },
-    "my-task": { "dependsOn": ["transit"] }
-  }
-}
-```
-
-**Why Transit Nodes work:**
-
-- `transit` creates dependency relationships without matching any actual script
-- Tasks that depend on `transit` gain dependency awareness
-- Since `transit` completes instantly (no script), tasks run in parallel
-- Cache correctly invalidates when dependency source code changes
-
-**How to identify tasks that need this pattern:** Look for tasks that read source files from dependencies but don't need their build outputs.
-
-## Missing outputs for File-Producing Tasks
-
-**Before flagging missing `outputs`, check what the task actually produces:**
-
-1. Read the package's script (e.g., `"build": "tsc"`, `"test": "vitest"`)
-2. Determine if it writes files to disk or only outputs to stdout
-3. Only flag if the task produces files that should be cached
-
-```json
-// WRONG - build produces files but they're not cached
-"build": {
-  "dependsOn": ["^build"]
-}
-
-// CORRECT - outputs are cached
-"build": {
-  "dependsOn": ["^build"],
-  "outputs": ["dist/**"]
-}
-```
-
-No `outputs` key is fine for stdout-only tasks. For file-producing tasks, missing `outputs` means Turbo has nothing to cache.
-
-## Forgetting ^ in dependsOn
-
-```json
-// WRONG - looks for "build" in SAME package (infinite loop or missing)
-"build": {
-  "dependsOn": ["build"]
-}
-
-// CORRECT - runs dependencies' build first
-"build": {
-  "dependsOn": ["^build"]
-}
-```
-
-The `^` means "in dependency packages", not "in this package".
-
-## Missing persistent on Dev Tasks
-
-```json
-// WRONG - dependent tasks hang waiting for dev to "finish"
-"dev": {
-  "cache": false
-}
-
-// CORRECT
-"dev": {
-  "cache": false,
-  "persistent": true
-}
-```
-
-## Package Config Missing extends
-
-```json
-// WRONG - packages/web/turbo.json
-{
-  "tasks": {
-    "build": { "outputs": [".next/**"] }
-  }
-}
-
-// CORRECT
-{
-  "extends": ["//"],
-  "tasks": {
-    "build": { "outputs": [".next/**"] }
-  }
-}
-```
-
-Without `"extends": ["//"]`, Package Configurations are invalid.
-
-## Root Tasks Need Special Syntax
-
-To run a task defined only in root `package.json`:
-
-```bash
-# WRONG
-turbo run format
-
-# CORRECT
-turbo run //#format
-```
-
-And in dependsOn:
-
-```json
-"build": {
-  "dependsOn": ["//#codegen"]  // Root package's codegen
-}
-```
-
-## Overwriting Default Inputs
-
-```json
-// WRONG - only watches test files, ignores source changes
-"test": {
-  "inputs": ["tests/**"]
-}
-
-// CORRECT - extends defaults, adds test files
-"test": {
-  "inputs": ["$TURBO_DEFAULT$", "tests/**"]
-}
-```
-
-Without `$TURBO_DEFAULT$`, you replace all default file watching.
-
-## Excluding `global.inputs` Without `$TURBO_DEFAULT$`
-
-When using `futureFlags.globalConfiguration`, `global.inputs` values are prepended to every task's inputs. If you want to exclude a global input from a specific task, you **must** include `$TURBO_DEFAULT$` to preserve default file hashing.
-
-```json
-// WRONG - task hashes NO files at all (global input cancelled, no defaults)
-"build": {
-  "inputs": ["!$TURBO_ROOT$/config.txt"]
-}
-
-// CORRECT - task hashes all package files, minus config.txt
-"build": {
-  "inputs": ["$TURBO_DEFAULT$", "!$TURBO_ROOT$/config.txt"]
-}
-```
-
-Without `$TURBO_DEFAULT$`, the only inclusion glob comes from `global.inputs`, which the negation cancels out. The task ends up with no inclusions and no default file hashing, so it hashes nothing. Changes to source files won't cause cache misses.
-
-## Caching Tasks with Side Effects
-
-```json
-// WRONG - deploy might be skipped on cache hit
-"deploy": {
-  "dependsOn": ["build"]
-}
-
-// CORRECT
-"deploy": {
-  "dependsOn": ["build"],
-  "cache": false
-}
-```
-
-Always disable cache for deploy, publish, or mutation tasks.

+ 0 - 325
neo/.agents/skills/turborepo/references/configuration/tasks.md

@@ -1,325 +0,0 @@
-# Task Configuration Reference
-
-Full docs: https://turborepo.dev/docs/reference/configuration#tasks
-
-## dependsOn
-
-Controls task execution order.
-
-```json
-{
-  "tasks": {
-    "build": {
-      "dependsOn": [
-        "^build", // Dependencies' build tasks first
-        "codegen", // Same package's codegen task first
-        "shared#build" // Specific package's build task
-      ]
-    }
-  }
-}
-```
-
-| Syntax     | Meaning                              |
-| ---------- | ------------------------------------ |
-| `^task`    | Run `task` in all dependencies first |
-| `task`     | Run `task` in same package first     |
-| `pkg#task` | Run specific package's task first    |
-
-The `^` prefix is crucial - without it, you're referencing the same package.
-
-### Transit Nodes for Parallel Tasks
-
-For tasks like `lint` and `check-types` that can run in parallel but need dependency-aware caching:
-
-```json
-{
-  "tasks": {
-    "transit": { "dependsOn": ["^transit"] },
-    "lint": { "dependsOn": ["transit"] },
-    "check-types": { "dependsOn": ["transit"] }
-  }
-}
-```
-
-**DO NOT use `dependsOn: ["^lint"]`** - this forces sequential execution.
-**DO NOT use `dependsOn: []`** - this breaks cache invalidation.
-
-The `transit` task creates dependency relationships without running anything (no matching script), so tasks run in parallel with correct caching.
-
-## outputs
-
-Glob patterns for files to cache. **If omitted, nothing is cached.**
-
-```json
-{
-  "tasks": {
-    "build": {
-      "outputs": ["dist/**", "build/**"]
-    }
-  }
-}
-```
-
-**Framework examples:**
-
-```json
-// Next.js
-"outputs": [".next/**", "!.next/cache/**"]
-
-// Vite
-"outputs": ["dist/**"]
-
-// TypeScript (tsc)
-"outputs": ["dist/**", "*.tsbuildinfo"]
-
-// No file outputs (lint, typecheck)
-"outputs": []
-```
-
-Use `!` prefix to exclude patterns from caching.
-
-## inputs
-
-Files considered when calculating task hash. Defaults to all tracked files in package.
-
-```json
-{
-  "tasks": {
-    "test": {
-      "inputs": ["src/**", "tests/**", "vitest.config.ts"]
-    }
-  }
-}
-```
-
-**Special values:**
-
-| Value                 | Meaning                                 |
-| --------------------- | --------------------------------------- |
-| `$TURBO_DEFAULT$`     | Include default inputs, then add/remove |
-| `$TURBO_ROOT$/<path>` | Reference files from repo root          |
-
-```json
-{
-  "tasks": {
-    "build": {
-      "inputs": [
-        "$TURBO_DEFAULT$",
-        "!README.md",
-        "$TURBO_ROOT$/tsconfig.base.json"
-      ]
-    }
-  }
-}
-```
-
-### Interaction with `global.inputs`
-
-When `futureFlags.globalConfiguration` is enabled, files listed in `global.inputs` are prepended to every task's `inputs`. The combined list is then used to compute the task hash.
-
-This is different from `globalDependencies`, where files were hashed into the **global** hash and could not be influenced by task-level `inputs`.
-
-**With `globalDependencies` (old behavior):**
-
-- `globalDependencies` files contribute to the global hash
-- Task `inputs` only control which **package** files are hashed
-- There is no way for a task to "opt out" of a `globalDependencies` file
-
-**With `global.inputs` (new behavior):**
-
-- `global.inputs` files are merged into each task's `inputs` globs
-- Task `inputs` and `global.inputs` are combined, then the full list is hashed into the **task** hash
-- Tasks can exclude specific global files with negation globs
-
-```json
-{
-  "futureFlags": { "globalConfiguration": true },
-  "global": {
-    "inputs": ["tsconfig.json", ".env"]
-  },
-  "tasks": {
-    "build": {},
-    "lint": {
-      "inputs": ["$TURBO_DEFAULT$", "!$TURBO_ROOT$/.env"]
-    }
-  }
-}
-```
-
-In this example:
-
-- `build` hashes all package files + `tsconfig.json` + `.env` (from `global.inputs`)
-- `lint` hashes all package files + `tsconfig.json`, but **excludes** `.env` because of the negation glob
-
-Tasks with no explicit `inputs` key still hash all package files (the default behavior) plus the `global.inputs` files.
-
-## env
-
-Environment variables to include in task hash.
-
-```json
-{
-  "tasks": {
-    "build": {
-      "env": [
-        "API_URL",
-        "NEXT_PUBLIC_*", // Wildcard matching
-        "!DEBUG" // Exclude from hash
-      ]
-    }
-  }
-}
-```
-
-Variables listed here affect cache hits - changing the value invalidates cache.
-
-## cache
-
-Enable/disable caching for a task. Default: `true`.
-
-```json
-{
-  "tasks": {
-    "dev": { "cache": false },
-    "deploy": { "cache": false }
-  }
-}
-```
-
-Disable for: dev servers, deploy commands, tasks with side effects.
-
-## persistent
-
-Mark long-running tasks that don't exit. Default: `false`.
-
-```json
-{
-  "tasks": {
-    "dev": {
-      "cache": false,
-      "persistent": true
-    }
-  }
-}
-```
-
-Required for dev servers - without it, dependent tasks wait forever.
-
-## interactive
-
-Allow task to receive stdin input. Default: `false`.
-
-```json
-{
-  "tasks": {
-    "login": {
-      "cache": false,
-      "interactive": true
-    }
-  }
-}
-```
-
-## outputLogs
-
-Control when logs are shown. Options: `full`, `hash-only`, `new-only`, `errors-only`, `none`.
-
-```json
-{
-  "tasks": {
-    "build": {
-      "outputLogs": "new-only" // Only show logs on cache miss
-    }
-  }
-}
-```
-
-## with
-
-Run tasks alongside this task. For long-running tasks that need runtime dependencies.
-
-```json
-{
-  "tasks": {
-    "dev": {
-      "with": ["api#dev"],
-      "persistent": true,
-      "cache": false
-    }
-  }
-}
-```
-
-Unlike `dependsOn`, `with` runs tasks concurrently (not sequentially). Use for dev servers that need other services running.
-
-## interruptible
-
-Allow `turbo watch` to restart the task on changes. Default: `false`.
-
-```json
-{
-  "tasks": {
-    "dev": {
-      "persistent": true,
-      "interruptible": true,
-      "cache": false
-    }
-  }
-}
-```
-
-Use for dev servers that don't automatically detect dependency changes.
-
-## description
-
-Human-readable description of the task.
-
-```json
-{
-  "tasks": {
-    "build": {
-      "description": "Compiles the application for production deployment"
-    }
-  }
-}
-```
-
-For documentation only - doesn't affect execution or caching.
-
-## passThroughEnv
-
-Environment variables available at runtime but NOT included in cache hash.
-
-```json
-{
-  "tasks": {
-    "build": {
-      "passThroughEnv": ["AWS_SECRET_KEY", "GITHUB_TOKEN"]
-    }
-  }
-}
-```
-
-**Warning**: Changes to these vars won't cause cache misses. Use `env` if changes should invalidate cache.
-
-## extends (Package Configuration only)
-
-Control task inheritance in Package Configurations.
-
-```json
-// packages/ui/turbo.json
-{
-  "extends": ["//"],
-  "tasks": {
-    "lint": {
-      "extends": false // Exclude from this package
-    }
-  }
-}
-```
-
-| Value            | Behavior                                                       |
-| ---------------- | -------------------------------------------------------------- |
-| `true` (default) | Inherit from root turbo.json                                   |
-| `false`          | Exclude task from package, or define fresh without inheritance |

+ 0 - 123
neo/.agents/skills/turborepo/references/environment/RULE.md

@@ -1,123 +0,0 @@
-# Environment Variables in Turborepo
-
-Turborepo provides fine-grained control over which environment variables affect task hashing and runtime availability.
-
-## Configuration Keys
-
-### `env` - Task-Specific Variables
-
-Variables that affect a specific task's hash. When these change, only that task rebuilds.
-
-```json
-{
-  "tasks": {
-    "build": {
-      "env": ["DATABASE_URL", "API_KEY"]
-    }
-  }
-}
-```
-
-### `globalEnv` - Variables Affecting All Tasks
-
-Variables that affect EVERY task's hash. When these change, all tasks rebuild.
-
-```json
-{
-  "globalEnv": ["CI", "NODE_ENV"]
-}
-```
-
-### `passThroughEnv` - Runtime-Only Variables (Not Hashed)
-
-Variables available at runtime but NOT included in hash. **Use with caution** - changes won't trigger rebuilds.
-
-```json
-{
-  "tasks": {
-    "deploy": {
-      "passThroughEnv": ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"]
-    }
-  }
-}
-```
-
-### `globalPassThroughEnv` - Global Runtime Variables
-
-Same as `passThroughEnv` but for all tasks.
-
-```json
-{
-  "globalPassThroughEnv": ["GITHUB_TOKEN"]
-}
-```
-
-## Wildcards and Negation
-
-### Wildcards
-
-Match multiple variables with `*`:
-
-```json
-{
-  "env": ["MY_API_*", "FEATURE_FLAG_*"]
-}
-```
-
-This matches `MY_API_URL`, `MY_API_KEY`, `FEATURE_FLAG_DARK_MODE`, etc.
-
-### Negation
-
-Exclude variables (useful with framework inference):
-
-```json
-{
-  "env": ["!NEXT_PUBLIC_ANALYTICS_ID"]
-}
-```
-
-## With `futureFlags.globalConfiguration`
-
-When the `globalConfiguration` future flag is enabled, global environment keys move under the `global` key with cleaner names:
-
-| Old (top-level)        | New (`global.`)  |
-| ---------------------- | ---------------- |
-| `globalEnv`            | `env`            |
-| `globalPassThroughEnv` | `passThroughEnv` |
-
-`global.env` and `global.passThroughEnv` behave identically to their top-level counterparts — they affect the global hash and all tasks, respectively. The rename is purely organizational.
-
-```json
-{
-  "futureFlags": { "globalConfiguration": true },
-  "global": {
-    "env": ["CI", "NODE_ENV"],
-    "passThroughEnv": ["GITHUB_TOKEN", "NPM_TOKEN"]
-  },
-  "tasks": {
-    "build": {
-      "env": ["DATABASE_URL", "API_*"],
-      "passThroughEnv": ["SENTRY_AUTH_TOKEN"]
-    }
-  }
-}
-```
-
-## Complete Example
-
-```json
-{
-  "$schema": "https://v2-8-21-canary-9.turborepo.dev/schema.json",
-  "globalEnv": ["CI", "NODE_ENV"],
-  "globalPassThroughEnv": ["GITHUB_TOKEN", "NPM_TOKEN"],
-  "tasks": {
-    "build": {
-      "env": ["DATABASE_URL", "API_*"],
-      "passThroughEnv": ["SENTRY_AUTH_TOKEN"]
-    },
-    "test": {
-      "env": ["TEST_DATABASE_URL"]
-    }
-  }
-}
-```

+ 0 - 175
neo/.agents/skills/turborepo/references/environment/gotchas.md

@@ -1,175 +0,0 @@
-# Environment Variable Gotchas
-
-Common mistakes and how to fix them.
-
-## .env Files Must Be in `inputs`
-
-Turbo does NOT read `.env` files. Your framework (Next.js, Vite, etc.) or `dotenv` loads them. But Turbo needs to know when they change.
-
-**Wrong:**
-
-```json
-{
-  "tasks": {
-    "build": {
-      "env": ["DATABASE_URL"]
-    }
-  }
-}
-```
-
-**Right:**
-
-```json
-{
-  "tasks": {
-    "build": {
-      "env": ["DATABASE_URL"],
-      "inputs": ["$TURBO_DEFAULT$", ".env", ".env.local", ".env.production"]
-    }
-  }
-}
-```
-
-## Strict Mode Filters CI Variables
-
-In strict mode, CI provider variables (GITHUB_TOKEN, GITLAB_CI, etc.) are filtered unless explicitly listed.
-
-**Symptom:** Task fails with "authentication required" or "permission denied" in CI.
-
-**Solution:**
-
-```json
-{
-  "globalPassThroughEnv": ["GITHUB_TOKEN", "GITLAB_CI", "CI"]
-}
-```
-
-## passThroughEnv Doesn't Affect Hash
-
-Variables in `passThroughEnv` are available at runtime but changes WON'T trigger rebuilds.
-
-**Dangerous example:**
-
-```json
-{
-  "tasks": {
-    "build": {
-      "passThroughEnv": ["API_URL"]
-    }
-  }
-}
-```
-
-If `API_URL` changes from staging to production, Turbo may serve a cached build pointing to the wrong API.
-
-**Use passThroughEnv only for:**
-
-- Auth tokens that don't affect output (SENTRY_AUTH_TOKEN)
-- CI metadata (GITHUB_RUN_ID)
-- Variables consumed after build (deploy credentials)
-
-## Runtime-Created Variables Are Invisible
-
-Turbo captures env vars at startup. Variables created during execution aren't seen.
-
-**Won't work:**
-
-```bash
-# In package.json scripts
-"build": "export API_URL=$COMPUTED_VALUE && next build"
-```
-
-**Solution:** Set vars before invoking turbo:
-
-```bash
-API_URL=$COMPUTED_VALUE turbo run build
-```
-
-## Different .env Files for Different Environments
-
-If you use `.env.development` and `.env.production`, both should be in inputs.
-
-```json
-{
-  "tasks": {
-    "build": {
-      "inputs": [
-        "$TURBO_DEFAULT$",
-        ".env",
-        ".env.local",
-        ".env.development",
-        ".env.development.local",
-        ".env.production",
-        ".env.production.local"
-      ]
-    }
-  }
-}
-```
-
-## Complete Next.js Example
-
-```json
-{
-  "$schema": "https://v2-8-21-canary-9.turborepo.dev/schema.json",
-  "globalEnv": ["CI", "NODE_ENV", "VERCEL"],
-  "globalPassThroughEnv": ["GITHUB_TOKEN", "VERCEL_URL"],
-  "tasks": {
-    "build": {
-      "dependsOn": ["^build"],
-      "env": ["DATABASE_URL", "NEXT_PUBLIC_*", "!NEXT_PUBLIC_ANALYTICS_ID"],
-      "passThroughEnv": ["SENTRY_AUTH_TOKEN"],
-      "inputs": [
-        "$TURBO_DEFAULT$",
-        ".env",
-        ".env.local",
-        ".env.production",
-        ".env.production.local"
-      ],
-      "outputs": [".next/**", "!.next/cache/**"]
-    }
-  }
-}
-```
-
-This config:
-
-- Hashes DATABASE*URL and NEXT_PUBLIC*\* vars (except analytics)
-- Passes through SENTRY_AUTH_TOKEN without hashing
-- Includes all .env file variants in the hash
-- Makes CI tokens available globally
-
-### With `futureFlags.globalConfiguration`
-
-The same config using the `global` key. The `.env` files move to `global.inputs`, which means they get folded into each task's hash individually rather than the global hash. This lets tasks exclude specific `.env` files if needed.
-
-```json
-{
-  "$schema": "https://v2-8-21-canary-9.turborepo.dev/schema.json",
-  "futureFlags": { "globalConfiguration": true },
-  "global": {
-    "env": ["CI", "NODE_ENV", "VERCEL"],
-    "passThroughEnv": ["GITHUB_TOKEN", "VERCEL_URL"],
-    "inputs": [".env", ".env.local", ".env.production", ".env.production.local"]
-  },
-  "tasks": {
-    "build": {
-      "dependsOn": ["^build"],
-      "env": ["DATABASE_URL", "NEXT_PUBLIC_*", "!NEXT_PUBLIC_ANALYTICS_ID"],
-      "passThroughEnv": ["SENTRY_AUTH_TOKEN"],
-      "outputs": [".next/**", "!.next/cache/**"]
-    }
-  }
-}
-```
-
-With this approach, a task that doesn't care about `.env.production` can exclude it:
-
-```json
-"lint": {
-  "inputs": ["$TURBO_DEFAULT$", "!$TURBO_ROOT$/.env.production"]
-}
-```
-
-This wouldn't have been possible with `globalDependencies`, where `.env.production` would be baked into the global hash and affect every task unconditionally.

+ 0 - 101
neo/.agents/skills/turborepo/references/environment/modes.md

@@ -1,101 +0,0 @@
-# Environment Modes
-
-Turborepo supports different modes for handling environment variables during task execution.
-
-## Strict Mode (Default)
-
-Only explicitly configured variables are available to tasks.
-
-**Behavior:**
-
-- Tasks only see vars listed in `env`, `globalEnv`, `passThroughEnv`, or `globalPassThroughEnv`
-- Unlisted vars are filtered out
-- Tasks fail if they require unlisted variables
-
-**Benefits:**
-
-- Guarantees cache correctness
-- Prevents accidental dependencies on system vars
-- Reproducible builds across machines
-
-```bash
-# Explicit (though it's the default)
-turbo run build --env-mode=strict
-```
-
-## Loose Mode
-
-All system environment variables are available to tasks.
-
-```bash
-turbo run build --env-mode=loose
-```
-
-**Behavior:**
-
-- Every system env var is passed through
-- Only vars in `env`/`globalEnv` affect the hash
-- Other vars are available but NOT hashed
-
-**Risks:**
-
-- Cache may restore incorrect results if unhashed vars changed
-- "Works on my machine" bugs
-- CI vs local environment mismatches
-
-**Use case:** Migrating legacy projects or debugging strict mode issues.
-
-## Framework Inference (Automatic)
-
-Turborepo automatically detects frameworks and includes their conventional env vars.
-
-### Inferred Variables by Framework
-
-| Framework        | Pattern             |
-| ---------------- | ------------------- |
-| Next.js          | `NEXT_PUBLIC_*`     |
-| Vite             | `VITE_*`            |
-| Create React App | `REACT_APP_*`       |
-| Gatsby           | `GATSBY_*`          |
-| Nuxt             | `NUXT_*`, `NITRO_*` |
-| Expo             | `EXPO_PUBLIC_*`     |
-| Astro            | `PUBLIC_*`          |
-| SvelteKit        | `PUBLIC_*`          |
-| Remix            | `REMIX_*`           |
-| Redwood          | `REDWOOD_ENV_*`     |
-| Sanity           | `SANITY_STUDIO_*`   |
-| Solid            | `VITE_*`            |
-
-### Disabling Framework Inference
-
-Globally via CLI:
-
-```bash
-turbo run build --framework-inference=false
-```
-
-Or exclude specific patterns in config:
-
-```json
-{
-  "tasks": {
-    "build": {
-      "env": ["!NEXT_PUBLIC_*"]
-    }
-  }
-}
-```
-
-### Why Disable?
-
-- You want explicit control over all env vars
-- Framework vars shouldn't bust the cache (e.g., analytics IDs)
-- Debugging unexpected cache misses
-
-## Checking Environment Mode
-
-Use `--dry` to see which vars affect each task:
-
-```bash
-turbo run build --dry=json | jq '.tasks[].environmentVariables'
-```

+ 0 - 148
neo/.agents/skills/turborepo/references/filtering/RULE.md

@@ -1,148 +0,0 @@
-# Turborepo Filter Syntax Reference
-
-## Running Only Changed Packages: `--affected`
-
-**The primary way to run only changed packages is `--affected`:**
-
-```bash
-# Run build/test/lint only in changed packages and their dependents
-turbo run build test lint --affected
-```
-
-This compares your current branch to the default branch (usually `main` or `master`) and runs tasks in:
-
-1. Packages with file changes
-2. Packages that depend on changed packages (dependents)
-
-### Why Include Dependents?
-
-If you change `@repo/ui`, packages that import `@repo/ui` (like `apps/web`) need to re-run their tasks to verify they still work with the changes.
-
-### Customizing --affected
-
-```bash
-# Use a different base branch
-turbo run build --affected --affected-base=origin/develop
-
-# Use a different head (current state)
-turbo run build --affected --affected-head=HEAD~5
-```
-
-### Common CI Pattern
-
-```yaml
-# .github/workflows/ci.yml
-- run: turbo run build test lint --affected
-```
-
-This is the most efficient CI setup - only run tasks for what actually changed.
-
----
-
-## Manual Git Comparison with --filter
-
-For more control, use `--filter` with git comparison syntax:
-
-```bash
-# Changed packages + dependents (same as --affected)
-turbo run build --filter=...[origin/main]
-
-# Only changed packages (no dependents)
-turbo run build --filter=[origin/main]
-
-# Changed packages + dependencies (packages they import)
-turbo run build --filter=[origin/main]...
-
-# Changed since last commit
-turbo run build --filter=...[HEAD^1]
-
-# Changed between two commits
-turbo run build --filter=[a1b2c3d...e4f5g6h]
-```
-
-### Comparison Syntax
-
-| Syntax        | Meaning                               |
-| ------------- | ------------------------------------- |
-| `[ref]`       | Packages changed since `ref`          |
-| `...[ref]`    | Changed packages + their dependents   |
-| `[ref]...`    | Changed packages + their dependencies |
-| `...[ref]...` | Dependencies, changed, AND dependents |
-
----
-
-## Other Filter Types
-
-Filters select which packages to include in a `turbo run` invocation.
-
-### Basic Syntax
-
-```bash
-turbo run build --filter=<package-name>
-turbo run build -F <package-name>
-```
-
-Multiple filters combine as a union (packages matching ANY filter run).
-
-### By Package Name
-
-```bash
---filter=web          # exact match
---filter=@acme/*      # scope glob
---filter=*-app        # name glob
-```
-
-### By Directory
-
-```bash
---filter=./apps/*           # all packages in apps/
---filter=./packages/ui      # specific directory
-```
-
-### By Dependencies/Dependents
-
-| Syntax      | Meaning                                |
-| ----------- | -------------------------------------- |
-| `pkg...`    | Package AND all its dependencies       |
-| `...pkg`    | Package AND all its dependents         |
-| `...pkg...` | Dependencies, package, AND dependents  |
-| `^pkg...`   | Only dependencies (exclude pkg itself) |
-| `...^pkg`   | Only dependents (exclude pkg itself)   |
-
-### Negation
-
-Exclude packages with `!`:
-
-```bash
---filter=!web              # exclude web
---filter=./apps/* --filter=!admin   # apps except admin
-```
-
-### Task Identifiers
-
-Run a specific task in a specific package:
-
-```bash
-turbo run web#build        # only web's build task
-turbo run web#build api#test   # web build + api test
-```
-
-### Combining Filters
-
-Multiple `--filter` flags create a union:
-
-```bash
-turbo run build --filter=web --filter=api   # runs in both
-```
-
----
-
-## Quick Reference: Changed Packages
-
-| Goal                               | Command                                                     |
-| ---------------------------------- | ----------------------------------------------------------- |
-| Changed + dependents (recommended) | `turbo run build --affected`                                |
-| Custom base branch                 | `turbo run build --affected --affected-base=origin/develop` |
-| Only changed (no dependents)       | `turbo run build --filter=[origin/main]`                    |
-| Changed + dependencies             | `turbo run build --filter=[origin/main]...`                 |
-| Since last commit                  | `turbo run build --filter=...[HEAD^1]`                      |

+ 0 - 152
neo/.agents/skills/turborepo/references/filtering/patterns.md

@@ -1,152 +0,0 @@
-# Common Filter Patterns
-
-Practical examples for typical monorepo scenarios.
-
-## Single Package
-
-Run task in one package:
-
-```bash
-turbo run build --filter=web
-turbo run test --filter=@acme/api
-```
-
-## Package with Dependencies
-
-Build a package and everything it depends on:
-
-```bash
-turbo run build --filter=web...
-```
-
-Useful for: ensuring all dependencies are built before the target.
-
-## Package Dependents
-
-Run in all packages that depend on a library:
-
-```bash
-turbo run test --filter=...ui
-```
-
-Useful for: testing consumers after changing a shared package.
-
-## Dependents Only (Exclude Target)
-
-Test packages that depend on ui, but not ui itself:
-
-```bash
-turbo run test --filter=...^ui
-```
-
-## Changed Packages
-
-Run only in packages with file changes since last commit:
-
-```bash
-turbo run lint --filter=[HEAD^1]
-```
-
-Since a specific branch point:
-
-```bash
-turbo run lint --filter=[main...HEAD]
-```
-
-## Changed + Dependents (PR Builds)
-
-Run in changed packages AND packages that depend on them:
-
-```bash
-turbo run build test --filter=...[HEAD^1]
-```
-
-Or use the shortcut:
-
-```bash
-turbo run build test --affected
-```
-
-## Directory-Based
-
-Run in all apps:
-
-```bash
-turbo run build --filter=./apps/*
-```
-
-Run in specific directories:
-
-```bash
-turbo run build --filter=./apps/web --filter=./apps/api
-```
-
-## Scope-Based
-
-Run in all packages under a scope:
-
-```bash
-turbo run build --filter=@acme/*
-```
-
-## Exclusions
-
-Run in all apps except admin:
-
-```bash
-turbo run build --filter=./apps/* --filter=!admin
-```
-
-Run everywhere except specific packages:
-
-```bash
-turbo run lint --filter=!legacy-app --filter=!deprecated-pkg
-```
-
-## Complex Combinations
-
-Apps that changed, plus their dependents:
-
-```bash
-turbo run build --filter=...[HEAD^1] --filter=./apps/*
-```
-
-All packages except docs, but only if changed:
-
-```bash
-turbo run build --filter=[main...HEAD] --filter=!docs
-```
-
-## Debugging Filters
-
-Use `--dry` to see what would run without executing:
-
-```bash
-turbo run build --filter=web... --dry
-```
-
-Use `--dry=json` for machine-readable output:
-
-```bash
-turbo run build --filter=...[HEAD^1] --dry=json
-```
-
-## CI/CD Patterns
-
-PR validation (most common):
-
-```bash
-turbo run build test lint --affected
-```
-
-Deploy only changed apps:
-
-```bash
-turbo run deploy --filter=./apps/* --filter=[main...HEAD]
-```
-
-Full rebuild of specific app and deps:
-
-```bash
-turbo run build --filter=production-app...
-```

+ 0 - 99
neo/.agents/skills/turborepo/references/watch/RULE.md

@@ -1,99 +0,0 @@
-# turbo watch
-
-Full docs: https://turborepo.dev/docs/reference/watch
-
-Re-run tasks automatically when code changes. Dependency-aware.
-
-```bash
-turbo watch [tasks]
-```
-
-## Basic Usage
-
-```bash
-# Watch and re-run build task when code changes
-turbo watch build
-
-# Watch multiple tasks
-turbo watch build test lint
-```
-
-Tasks re-run in order configured in `turbo.json` when source files change.
-
-## With Persistent Tasks
-
-Persistent tasks (`"persistent": true`) won't exit, so they can't be depended on. They work the same in `turbo watch` as `turbo run`.
-
-### Dependency-Aware Persistent Tasks
-
-If your tool has built-in watching (like `next dev`), use its watcher:
-
-```json
-{
-  "tasks": {
-    "dev": {
-      "persistent": true,
-      "cache": false
-    }
-  }
-}
-```
-
-### Non-Dependency-Aware Tools
-
-For tools that don't detect dependency changes, use `interruptible`:
-
-```json
-{
-  "tasks": {
-    "dev": {
-      "persistent": true,
-      "interruptible": true,
-      "cache": false
-    }
-  }
-}
-```
-
-`turbo watch` will restart interruptible tasks when dependencies change.
-
-## Limitations
-
-### Caching
-
-Caching is experimental with watch mode:
-
-```bash
-turbo watch your-tasks --experimental-write-cache
-```
-
-### Task Outputs in Source Control
-
-If tasks write files tracked by git, watch mode may loop infinitely. Watch mode uses file hashes to prevent this but it's not foolproof.
-
-**Recommendation**: Remove task outputs from git.
-
-## vs turbo run
-
-| Feature           | `turbo run` | `turbo watch` |
-| ----------------- | ----------- | ------------- |
-| Runs once         | Yes         | No            |
-| Re-runs on change | No          | Yes           |
-| Caching           | Full        | Experimental  |
-| Use case          | CI, one-off | Development   |
-
-## Common Patterns
-
-### Development Workflow
-
-```bash
-# Run dev servers and watch for build changes
-turbo watch dev build
-```
-
-### Type Checking During Development
-
-```bash
-# Watch and re-run type checks
-turbo watch check-types
-```

+ 44 - 102
neo/.agents/skills/una-api/SKILL.md

@@ -1,120 +1,62 @@
 ---
 name: una-api
-description: UNA CMS API contracts for NEO—guest-safe blocks, JSON responses, endpoint shape, and coordination with packages/app fetcher. Use when editing UNA PHP blocks/services, debugging blank pages from API failures, or integrating new UNA endpoints with the NEO monorepo.
-metadata:
-  author: neo
-  version: "1.0.0"
+description: How UNA CMS services must behave for NEO (guest-safe blocks, JSON-only responses, api.php contract) and how NEO calls them. Use when editing UNA PHP blocks or services that NEO renders, adding a new UNA endpoint for NEO, or debugging a blank or maintenance-mode NEO page caused by an API failure.
 ---
 
-# UNA CMS API — NEO integration
+# UNA API for NEO
 
-This skill describes how **UNA CMS** backends must behave so the **NEO** frontend (Yarn monorepo: `apps/next`, `apps/expo`, shared `packages/app`) receives **parseable JSON**, not PHP fatals or HTML error pages.
+NEO renders pages that UNA describes. Each app has one catch-all route that asks UNA for the page (`system/get_page_by_request/TemplServicePages`) and renders its blocks. The rest of the data comes from UNA services called through `api.php`. When a service prints a PHP fatal or an HTML error page instead of JSON, NEO can't render the page.
 
-## NEO frontend expectations
+UNA's PHP code is the rest of the UNA repo, one level above `neo/`. Paths below that start with `api.php`, `inc/` or `modules/` are UNA paths. Client forks of unacms/neo don't contain them.
 
-- **All UNA API calls from app code** go through [`packages/app/lib/fetcher.ts`](../../../packages/app/lib/fetcher.ts) (`import { fetcher } from 'app/lib/fetcher'`). It normalizes hosts for web vs native (`use_proxy_web`, `use_proxy_native`, `UNA_URL`, `APP_URL`, etc.) and parses **JSON** responses.
-- **Endpoint shape:** `/api.php?r=module/action/Template` with params as required by UNA (often `params[]=...` with JSON). Do not invent ad hoc REST shapes without aligning with existing NEO usage.
-- **Page data:** NEO loads page payloads via UNA (e.g. flows using `get_page_by_request`). Responses must include page metadata, blocks configuration, and user context **without** assuming a logged-in user everywhere.
+## The api.php contract
 
-## Critical issue — guest (non-logged-in) users
+- Route: `/api.php?r={module}/{method}/{class}`. The class defaults to `Module`; system services use `Templ*` classes, for example `system/get_page_by_request/TemplServicePages`.
+- Parameters: repeated `params[]=...`, or one JSON array `params=[...]`.
+- Only services the module marks safe (`is_safe_service`) or public (`is_public_service`) can be called. Anything else returns 403 `{status: 403, error}` unless `sys_api_access_unsafe_services` is on.
+- Success: `{status: 200, module, method, params, data, hash}`. NEO reads `data`.
+- A service that returns `['error' => ..., 'code' => ..., 'desc' => ...]` produces HTTP 500 with `{status, error, data}` (plus `code` when given).
+- Page blocks arrive in `data.elements`, keyed by cell (`cell_center`, ...).
 
-UNA still evaluates **all** configured blocks for a page when the user is a **guest**. If any block assumes a logged-in user and calls methods on `false` / null, PHP throws.
+## How NEO calls it
 
-### What breaks NEO
+- Client code calls UNA through `fetcher` from `app/lib/fetcher` (`packages/app/lib/fetcher.ts`):
+  - `fetcher(path)` does a GET.
+  - `fetcher([path, token, body])` POSTs `body`.
+  - It appends `&lang=`, so the path must already contain `?r=`.
+  - On web it calls same-origin `/api/...`; `apps/next/proxy.js` forwards that to UNA, adds `Authorization: Bearer UNA_API_KEY` and passes the viewer's cookies.
+  - Native calls `UNA_URL` directly with an `Origin: APP_ORIGIN` header and the session cookie. Native has no API key.
+  - If the body isn't JSON, `fetcher` returns `{}`, so a broken service shows up as missing data, not as an error.
+- Many endpoints come from UNA data rather than code: `'/api.php?r=' + data.request_url`. Fixed ones live in `appSetting('urls', ...)` (`packages/app/settings/configs.js`).
+- Three places skip `fetcher` on purpose:
+  - The web page shell (`apps/next/app/[...path]/page.js`) fetches the page JSON on the server with the API key and the viewer's cookies.
+  - AI chat streaming (`ui/molecules/ai-agent/helper.js`) reads server-sent events.
+  - Direct file uploads (`lib/util/upload.ts`).
+- If the page JSON can't be parsed, the web shell logs the PHP output and renders the page as `page_status: 503`. `Layout` then shows the maintenance screen for the whole page. The home page falls back to a static splash. Fix the PHP; the fallback is only a safety net.
 
-1. UNA returns an **HTML** error page (or PHP fatal output) instead of JSON.
-2. `fetcher` / client code cannot parse JSON → whole route can fail.
-3. Users may see a **blank page** (including static areas like splash).
+## Guests: the usual cause of broken pages
 
-**Example (anti-pattern):**
+For a guest, UNA evaluates every block whose `visible_for_levels` includes the guest level. A block that calls a method on a missing profile then fatals, and the whole page response stops being JSON.
 
 ```php
-// BAD — crashes when user is not logged in
-$user->isOnline();
-
-// GOOD — guard first
-$user && $user->isOnline();
-```
-
-## Best practices for UNA blocks and services
-
-### 1. Always guard user / profile context
-
-```php
-public function serviceGetBlockContacts() {
-    $oProfile = BxDolProfile::getInstance();
-
-    if (!$oProfile) {
-        return array('content' => '', 'menu' => '');
-    }
-
-    $bOnline = $oProfile->isOnline();
-    // ...
-}
-```
-
-### 2. Block visibility (`visible_for`)
-
-- Use `BX_DOL_PG_MEMBERS` for blocks that require a logged-in user.
-- Use `BX_DOL_PG_ALL` only if the block code **explicitly** handles guests.
-
-### 3. Graceful degradation in templates
-
-Return empty states or safe defaults instead of throwing when profile/user is missing.
-
-### 4. Defensive service methods
-
-Check module enabled, then logged profile id (`bx_get_logged_profile_id()` or equivalent), return `''` or empty structures for guests when appropriate.
-
-## API response shape
-
-**Success** responses should be JSON NEO can consume, e.g.:
-
-```json
-{
-  "data": {
-    "title": "Page Title",
-    "uri": "/page-uri",
-    "logged": 0,
-    "blocks": { }
-  },
-  "code": 200
-}
+// Bad: fatal for guests
+$oProfile = BxDolProfile::getInstance();
+$bOnline = $oProfile->isOnline();
+
+// Good: getInstance() returns false for guests
+$oProfile = BxDolProfile::getInstance();
+if (!$oProfile)
+    return '';
+$bOnline = $oProfile->isOnline();
 ```
 
-**Never** return raw PHP error HTML inside an API response body for routes NEO expects as JSON.
-
-## Page-specific notes
-
-- **Home / guest splash:** Prefer blocks that work without auth; keep messenger, notifications, and user-only blocks on **members** visibility.
-- **Profile / content:** Respect permissions; return empty or restricted states instead of errors.
-
-## Testing checklist (UNA side)
-
-- [ ] Logged-out (guest) session
-- [ ] Logged-in user
-- [ ] Different permission levels
-- [ ] Network tab: response is JSON, no PHP error text in body
-
-## Debugging on UNA servers
-
-- Log errors; avoid `display_errors` on responses used as APIs.
-- Use server error logs (paths vary by hosting).
-
-## NEO frontend workaround (not a substitute for fixing UNA)
-
-The web app may handle API errors on specific routes (e.g. home) with fallback UI—see **[`apps/next/app/[...path]/page.js`](../../../apps/next/app/[...path]/page.js)** for graceful handling when `data.code` indicates server errors. **Fix the root cause in UNA**; the workaround is a safety net.
-
-## Summary
-
-| Do | Don't |
-|----|-------|
-| Guard `$oProfile` / user before use | Assume user is always logged in |
-| Return empty content for guests | Let PHP errors reach the response body |
-| Configure `visible_for` appropriately | Expose member-only blocks to guests without safe code |
-| Log errors server-side | Emit HTML fatal errors on JSON API routes |
+- Guard every profile or user access: `BxDolProfile::getInstance()` returns false for guests, and so does `bx_get_logged_profile_id()`.
+- Return empty content (`''` or an empty array) for guests instead of throwing.
+- Block visibility is the `visible_for_levels` bitmask in `sys_pages_blocks`. Bit `2^(level_id - 1)` enables a member level; the guest level is id 1, so bit value 1 (`inc/classes/BxDolAcl.php`). Members-only blocks use `2147483646` (every level except guest). Give a block guest visibility only when its code handles guests.
+- Don't confuse this with `BX_DOL_PG_MEMBERS` / `BX_DOL_PG_ALL`: those are content privacy groups (`inc/classes/BxDolPrivacy.php`), not block visibility.
 
-## Related NEO docs
+## Checks for a UNA change NEO uses
 
-- Root agent guide: [`agents.md`](../../../agents.md) (UNA CMS API Integration, precedence).
-- Project rules: [`.cursorrules`](../../../.cursorrules).
+- Load the page as a guest and as a member, and as a member of a restricted level if the block is restricted.
+- In the network tab, every `api.php` response body is JSON with no PHP warnings or HTML in it. Turn `display_errors` off for API traffic and read the server error log instead.
+- New services NEO calls must be marked safe or public, or the call returns 403.

+ 0 - 2122
neo/.agents/skills/uniwind/SKILL.md

@@ -1,2122 +0,0 @@
----
-name: uniwind
-description: >
-  Uniwind — Tailwind CSS v4 styling for React Native. Use when adding, building,
-  or styling components in a React Native project that uses Tailwind with className.
-  Triggers on: className on RN components, Tailwind classes in RN, global.css with
-  @import 'uniwind', withUniwindConfig, withUniwind, metro.config.js with Uniwind,
-  useResolveClassNames, useCSSVariable, getCSSVariable, useUniwind, dark:/light: theming, platform
-  selectors (ios:/android:/native:/web:/tv:), data-[prop=value], responsive breakpoints
-  (sm:/md:/lg:), tailwind-variants, tv() variants, ScopedTheme, Uniwind.setTheme,
-  Uniwind.updateCSSVariables, @theme, @utility, @variant, CSS variables in RN,
-  colorClassName, tintColorClassName, contentContainerClassName, Uniwind Pro
-  (animations, transitions, shadow tree, native insets), safe area utilities,
-  gradients, hairlineWidth(), fontScale(), pixelRatio(), light-dark(), OKLCH,
-  cn, tailwind-merge, HeroUI Native, react-native-reusables, Gluestack.
-  Does NOT handle migration — use migrate-nativewind-to-uniwind skill.
----
-
-# Uniwind — Complete Reference
-
-> Uniwind 1.6.0+ / Tailwind CSS v4 / React Native 0.81+ / Expo SDK 54+
-
-If user has lower version, recommend updating to 1.6.0+ for best experience.
-
-**NEO monorepo:** Native app styling is **Uniwind** (Tailwind v4), not NativeWind. Stack notes live in the repo root [`uniwind.md`](../../../uniwind.md). The mentions of NativeWind below are for **migration**, compatibility (`rem` base), or comparisons—not the current NEO default.
-
-Uniwind brings Tailwind CSS v4 to React Native. All core React Native components support the `className` prop out of the box. Styles are compiled at build time — no runtime overhead.
-
-## Critical Rules
-
-1. **Tailwind v4 only** — Use `@import 'tailwindcss'` not `@tailwind base`. Tailwind v3 is not supported.
-2. **Never construct classNames dynamically** — Tailwind scans at build time. `bg-${color}-500` will NOT work. Use complete string literals, mapping objects, or ternaries.
-3. **Never use `cssInterop` or `remapProps`** — Those are NativeWind APIs. Uniwind does not override global components.
-4. **No `tailwind.config.js`** — All config goes in `global.css` via `@theme` and `@layer theme`.
-5. **No ThemeProvider required** — Use `Uniwind.setTheme()` directly.
-6. **`withUniwindConfig` must be the outermost** Metro config wrapper.
-7. **NEVER wrap `react-native` or `react-native-reanimated` components with `withUniwind`** — `View`, `Text`, `Pressable`, `Image`, `TextInput`, `ScrollView`, `FlatList`, `Switch`, `Modal`, `Animated.View`, `Animated.Text`, etc. already have full `className` support built in. Wrapping them with `withUniwind` will break behavior. Only use `withUniwind` for **third-party** components (e.g., `expo-image`, `expo-blur`, `moti`).
-8. **Font families: single font only** — React Native doesn't support fallbacks. Use `--font-sans: 'Roboto-Regular'` not `'Roboto', sans-serif`.
-9. **All theme variants must define the same set of CSS variables** — If `light` defines `--color-primary`, then `dark` and every custom theme must too. Mismatched variables cause runtime errors.
-10. **`accent-` prefix is REQUIRED for non-style color props** — This is crucial. Props like `color` (Button, ActivityIndicator), `tintColor` (Image), `thumbColor` (Switch), `placeholderTextColor` (TextInput) are NOT part of the `style` object. You MUST use the corresponding `{propName}ClassName` prop with `accent-` prefixed classes. Example: `<ActivityIndicator colorClassName="accent-blue-500" />` NOT `<ActivityIndicator className="text-blue-500" />`. Regular Tailwind color classes (like `text-blue-500`) only work on `className` (which maps to `style`). For non-style color props, always use `accent-`.
-11. **rem default is 16px** — When migrating from NativeWind (which used 14px), set `polyfills: { rem: 14 }` in Metro; NEO keeps this for spacing parity unless intentionally changed.
-12. **`cssEntryFile` must be a relative path string** — Use `'./global.css'` not `path.resolve(__dirname, 'global.css')`.
-13. **Deduplicate with `cn()` when mixing custom CSS classes and Tailwind** — Uniwind does NOT auto-deduplicate. If a custom CSS class (`.card { padding: 16px }`) and a Tailwind utility (`p-6`) set the same property, both apply with unpredictable results. Always wrap with `cn('card', 'p-6')` when there's overlap.
-
-## Setup
-
-### Installation
-
-```bash
-# or other package manager
-bun install uniwind tailwindcss
-```
-
-Requires **Tailwind CSS v4+**.
-
-### global.css
-
-Create a CSS entry file:
-
-```css
-@import 'tailwindcss';
-@import 'uniwind';
-```
-
-Import in your **App component** (e.g., `App.tsx` or `app/_layout.tsx`), **NOT** in `index.ts`/`index.js` — importing there breaks hot reload:
-
-```tsx
-// app/_layout.tsx or App.tsx
-import './global.css';
-```
-
-The directory containing `global.css` is the app root — Tailwind scans for classNames starting from this directory.
-
-### Metro Configuration
-
-```js
-const { getDefaultConfig } = require('expo/metro-config');
-// Bare RN: const { getDefaultConfig } = require('@react-native/metro-config');
-const { withUniwindConfig } = require('uniwind/metro');
-
-const config = getDefaultConfig(__dirname);
-
-// withUniwindConfig MUST be the OUTERMOST wrapper
-module.exports = withUniwindConfig(config, {
-  cssEntryFile: './global.css',           // Required — relative path from project root
-  polyfills: { rem: 16 },                // Optional — base rem value (default 16)
-  extraThemes: ['ocean', 'sunset'],       // Optional — custom themes beyond light/dark
-  dtsFile: './uniwind-types.d.ts',        // Optional — TypeScript types output path
-  debug: true,                            // Optional — log unsupported CSS in dev
-  isTV: false,                            // Optional — enable TV platform support
-});
-```
-
-For most flows, keep defaults, only provide `cssEntryFile`.
-
-Wrapper order — Uniwind must wrap everything else:
-
-```js
-// CORRECT
-module.exports = withUniwindConfig(withOtherConfig(config, opts), { cssEntryFile: './global.css' });
-
-// WRONG — Uniwind is NOT outermost
-module.exports = withOtherConfig(withUniwindConfig(config, { cssEntryFile: './global.css' }), opts);
-```
-
-### Vite Configuration (v1.2.0+)
-
-If user has storybook setup, add extra vite config:
-
-```ts
-import tailwindcss from '@tailwindcss/vite';
-import { uniwind } from 'uniwind/vite';
-import { defineConfig } from 'vite';
-
-export default defineConfig({
-  plugins: [
-    tailwindcss(),
-    uniwind({
-      cssEntryFile: './src/global.css',
-      dtsFile: './src/uniwind-types.d.ts',
-    }),
-  ],
-});
-```
-
-### TypeScript
-
-Uniwind auto-generates a `.d.ts` file (default: `./uniwind-types.d.ts`) after running Metro. Place it in `src/` or `app/` for auto-inclusion, or add to `tsconfig.json`:
-
-```json
-{ "include": ["./uniwind-types.d.ts"] }
-```
-
-If user has some typescript errors related to classNames, just run metro server to build the d.ts file.
-
-### Expo Router Placement
-
-```text
-project/
-├── app/_layout.tsx    ← import '../global.css' here
-├── components/
-├── global.css         ← project root (best location)
-└── metro.config.js    ← cssEntryFile: './global.css'
-```
-
-If `global.css` is in `app/` dir, add `@source` for sibling directories:
-
-```css
-@import 'tailwindcss';
-@import 'uniwind';
-@source '../components';
-```
-
-### Tailwind IntelliSense (VS Code / Cursor / Windsurf)
-
-```json
-{
-  "tailwindCSS.classAttributes": [
-    "class", "className", "headerClassName",
-    "contentContainerClassName", "columnWrapperClassName",
-    "endFillColorClassName", "imageClassName", "tintColorClassName",
-    "ios_backgroundColorClassName", "thumbColorClassName",
-    "trackColorOnClassName", "trackColorOffClassName",
-    "selectionColorClassName", "cursorColorClassName",
-    "underlineColorAndroidClassName", "placeholderTextColorClassName",
-    "selectionHandleColorClassName", "colorsClassName",
-    "progressBackgroundColorClassName", "titleColorClassName",
-    "underlayColorClassName", "colorClassName",
-    "backdropColorClassName", "backgroundColorClassName",
-    "statusBarBackgroundColorClassName", "drawerBackgroundColorClassName",
-    "ListFooterComponentClassName", "ListHeaderComponentClassName"
-  ],
-  "tailwindCSS.classFunctions": ["useResolveClassNames"]
-}
-```
-
-### Monorepo Support
-
-Add `@source` directives in `global.css` for packages outside the CSS entry file's directory:
-
-```css
-@import 'tailwindcss';
-@import 'uniwind';
-@source "../../packages/ui/src";
-@source "../../packages/shared/src";
-```
-
-Also needed for `node_modules` packages that contain Uniwind classes (e.g., shared UI libraries).
-
-## Component Bindings
-
-All core React Native components support `className` out of the box. Some have additional className props for sub-styles (like `contentContainerClassName`) and non-style color props (requiring `accent-` prefix).
-
-### Complete Reference
-
-**Legend**: Props marked with ⚡ require the `accent-` prefix. Props in parentheses are platform-specific.
-
-#### View
-
-| Prop | Maps to | Prefix |
-|------|---------|--------|
-| `className` | `style` | — |
-
-#### Text
-
-| Prop | Maps to | Prefix |
-|------|---------|--------|
-| `className` | `style` | — |
-| `selectionColorClassName` | `selectionColor` | ⚡ `accent-` |
-
-#### Pressable
-
-| Prop | Maps to | Prefix |
-|------|---------|--------|
-| `className` | `style` | — |
-
-Supports `active:`, `disabled:`, `focus:` state selectors.
-
-#### Image
-
-| Prop | Maps to | Prefix |
-|------|---------|--------|
-| `className` | `style` | — |
-| `tintColorClassName` | `tintColor` | ⚡ `accent-` |
-
-#### TextInput
-
-| Prop | Maps to | Prefix |
-|------|---------|--------|
-| `className` | `style` | — |
-| `cursorColorClassName` | `cursorColor` | ⚡ `accent-` |
-| `selectionColorClassName` | `selectionColor` | ⚡ `accent-` |
-| `placeholderTextColorClassName` | `placeholderTextColor` | ⚡ `accent-` |
-| `selectionHandleColorClassName` | `selectionHandleColor` | ⚡ `accent-` |
-| `underlineColorAndroidClassName` | `underlineColorAndroid` (Android) | ⚡ `accent-` |
-
-Supports `focus:`, `active:`, `disabled:` state selectors.
-
-#### ScrollView
-
-| Prop | Maps to | Prefix |
-|------|---------|--------|
-| `className` | `style` | — |
-| `contentContainerClassName` | `contentContainerStyle` | — |
-| `endFillColorClassName` | `endFillColor` | ⚡ `accent-` |
-
-#### FlatList
-
-| Prop | Maps to | Prefix |
-|------|---------|--------|
-| `className` | `style` | — |
-| `contentContainerClassName` | `contentContainerStyle` | — |
-| `columnWrapperClassName` | `columnWrapperStyle` | — |
-| `ListHeaderComponentClassName` | `ListHeaderComponentStyle` | — |
-| `ListFooterComponentClassName` | `ListFooterComponentStyle` | — |
-| `endFillColorClassName` | `endFillColor` | ⚡ `accent-` |
-
-#### SectionList
-
-| Prop | Maps to | Prefix |
-|------|---------|--------|
-| `className` | `style` | — |
-| `contentContainerClassName` | `contentContainerStyle` | — |
-| `ListHeaderComponentClassName` | `ListHeaderComponentStyle` | — |
-| `ListFooterComponentClassName` | `ListFooterComponentStyle` | — |
-| `endFillColorClassName` | `endFillColor` | ⚡ `accent-` |
-
-#### VirtualizedList
-
-| Prop | Maps to | Prefix |
-|------|---------|--------|
-| `className` | `style` | — |
-| `contentContainerClassName` | `contentContainerStyle` | — |
-| `ListHeaderComponentClassName` | `ListHeaderComponentStyle` | — |
-| `ListFooterComponentClassName` | `ListFooterComponentStyle` | — |
-| `endFillColorClassName` | `endFillColor` | ⚡ `accent-` |
-
-#### Switch
-
-| Prop | Maps to | Prefix |
-|------|---------|--------|
-| `thumbColorClassName` | `thumbColor` | ⚡ `accent-` |
-| `trackColorOnClassName` | `trackColor.true` (on) | ⚡ `accent-` |
-| `trackColorOffClassName` | `trackColor.false` (off) | ⚡ `accent-` |
-| `ios_backgroundColorClassName` | `ios_backgroundColor` (iOS) | ⚡ `accent-` |
-
-Note: Switch does NOT support `className` (`className?: never` in types). Use only the color-specific className props above. Supports `disabled:` state selector.
-
-#### ActivityIndicator
-
-| Prop | Maps to | Prefix |
-|------|---------|--------|
-| `className` | `style` | — |
-| `colorClassName` | `color` | ⚡ `accent-` |
-
-#### Button
-
-| Prop | Maps to | Prefix |
-|------|---------|--------|
-| `colorClassName` | `color` | ⚡ `accent-` |
-
-Note: Button does not support `className` (no `style` prop on RN Button).
-
-#### Modal
-
-| Prop | Maps to | Prefix |
-|------|---------|--------|
-| `className` | `style` | — |
-| `backdropColorClassName` | `backdropColor` | ⚡ `accent-` |
-
-#### RefreshControl
-
-| Prop | Maps to | Prefix |
-|------|---------|--------|
-| `className` | `style` | — |
-| `colorsClassName` | `colors` (Android) | ⚡ `accent-` |
-| `tintColorClassName` | `tintColor` (iOS) | ⚡ `accent-` |
-| `titleColorClassName` | `titleColor` (iOS) | ⚡ `accent-` |
-| `progressBackgroundColorClassName` | `progressBackgroundColor` (Android) | ⚡ `accent-` |
-
-#### ImageBackground
-
-| Prop | Maps to | Prefix |
-|------|---------|--------|
-| `className` | `style` | — |
-| `imageClassName` | `imageStyle` | — |
-| `tintColorClassName` | `tintColor` | ⚡ `accent-` |
-
-#### SafeAreaView
-
-| Prop | Maps to | Prefix |
-|------|---------|--------|
-| `className` | `style` | — |
-
-#### KeyboardAvoidingView
-
-| Prop | Maps to | Prefix |
-|------|---------|--------|
-| `className` | `style` | — |
-| `contentContainerClassName` | `contentContainerStyle` | — |
-
-#### InputAccessoryView
-
-| Prop | Maps to | Prefix |
-|------|---------|--------|
-| `className` | `style` | — |
-| `backgroundColorClassName` | `backgroundColor` | ⚡ `accent-` |
-
-#### TouchableHighlight
-
-| Prop | Maps to | Prefix |
-|------|---------|--------|
-| `className` | `style` | — |
-| `underlayColorClassName` | `underlayColor` | ⚡ `accent-` |
-
-Supports `active:`, `disabled:` state selectors.
-
-#### TouchableOpacity
-
-| Prop | Maps to | Prefix |
-|------|---------|--------|
-| `className` | `style` | — |
-
-Supports `active:`, `disabled:` state selectors.
-
-#### TouchableNativeFeedback
-
-| Prop | Maps to | Prefix |
-|------|---------|--------|
-| `className` | `style` | — |
-
-Supports `active:`, `disabled:` state selectors.
-
-#### TouchableWithoutFeedback
-
-| Prop | Maps to | Prefix |
-|------|---------|--------|
-| `className` | `style` | — |
-
-Supports `active:`, `disabled:` state selectors.
-
-### Usage Examples
-
-```tsx
-import { View, Text, Pressable, TextInput, ScrollView, FlatList, Switch, Image, ActivityIndicator, Modal, RefreshControl, Button } from 'react-native';
-
-// View — basic layout
-<View className="flex-1 bg-background p-4">
-  <Text className="text-foreground text-lg font-bold">Title</Text>
-</View>
-
-// Pressable — with press/focus states
-<Pressable className="bg-primary px-6 py-3 rounded-lg active:opacity-80 active:bg-primary/90 focus:ring-2">
-  <Text className="text-white text-center font-semibold">Press Me</Text>
-</Pressable>
-
-// TextInput — with focus state and accent- color props
-<TextInput
-  className="border border-border rounded-lg px-4 py-2 text-base text-foreground focus:border-primary"
-  placeholderTextColorClassName="accent-muted"
-  selectionColorClassName="accent-primary"
-  cursorColorClassName="accent-primary"
-  selectionHandleColorClassName="accent-primary"
-  underlineColorAndroidClassName="accent-transparent"
-  placeholder="Enter text..."
-/>
-
-// ScrollView — with content container
-<ScrollView className="flex-1" contentContainerClassName="p-4 gap-4">
-  {/* content */}
-</ScrollView>
-
-// FlatList — with all sub-style props
-<FlatList
-  className="flex-1"
-  contentContainerClassName="p-4 gap-3"
-  columnWrapperClassName="gap-3"
-  ListHeaderComponentClassName="pb-4"
-  ListFooterComponentClassName="pt-4"
-  endFillColorClassName="accent-gray-100"
-  numColumns={2}
-  data={items}
-  renderItem={({ item }) => <ItemCard item={item} />}
-/>
-
-// Switch — no className support, use color-specific props only
-<Switch
-  thumbColorClassName="accent-white"
-  trackColorOnClassName="accent-primary"
-  trackColorOffClassName="accent-gray-300 dark:accent-gray-700"
-  ios_backgroundColorClassName="accent-gray-200"
-/>
-
-// Image — tint color
-<Image className="w-6 h-6" tintColorClassName="accent-primary" source={icon} />
-
-// ActivityIndicator
-<ActivityIndicator className="m-4" colorClassName="accent-primary" size="large" />
-
-// Button — only colorClassName (no className)
-<Button colorClassName="accent-primary" title="Submit" onPress={handleSubmit} />
-
-// Modal — backdrop color
-<Modal className="flex-1" backdropColorClassName="accent-black/50">
-  {/* content */}
-</Modal>
-
-// RefreshControl — platform-specific color props
-<RefreshControl
-  className="p-4"
-  tintColorClassName="accent-primary"
-  titleColorClassName="accent-gray-500"
-  colorsClassName="accent-primary"
-  progressBackgroundColorClassName="accent-white dark:accent-gray-800"
-/>
-
-// ImageBackground — separate image styling
-<ImageBackground
-  className="flex-1 justify-center items-center"
-  imageClassName="opacity-50"
-  tintColorClassName="accent-blue-500"
-  source={bgImage}
->
-  <Text className="text-white text-2xl font-bold">Overlay</Text>
-</ImageBackground>
-
-// KeyboardAvoidingView
-<KeyboardAvoidingView
-  behavior="padding"
-  className="flex-1 bg-white"
-  contentContainerClassName="p-4 justify-end"
->
-  <TextInput className="border border-gray-300 rounded-lg p-3" placeholder="Type..." />
-</KeyboardAvoidingView>
-
-// InputAccessoryView
-<InputAccessoryView
-  className="p-4 border-t border-gray-300"
-  backgroundColorClassName="accent-white dark:accent-gray-800"
->
-  <Button title="Done" onPress={dismissKeyboard} />
-</InputAccessoryView>
-
-// TouchableHighlight — underlay color
-<TouchableHighlight
-  className="bg-blue-500 px-6 py-3 rounded-lg"
-  underlayColorClassName="accent-blue-600 dark:accent-blue-700"
-  onPress={handlePress}
->
-  <Text className="text-white font-semibold">Press Me</Text>
-</TouchableHighlight>
-```
-
-## The accent- Prefix Pattern
-
-React Native components have props like `color`, `tintColor`, `thumbColor` that are NOT part of the `style` object. To set these via Tailwind classes, use the `accent-` prefix with the corresponding `{propName}ClassName` prop:
-
-```tsx
-// color prop → colorClassName with accent- prefix
-<ActivityIndicator colorClassName="accent-blue-500 dark:accent-blue-400" />
-<Button colorClassName="accent-primary" title="Submit" />
-
-// tintColor prop → tintColorClassName
-<Image className="w-6 h-6" tintColorClassName="accent-red-500" source={icon} />
-
-// thumbColor → thumbColorClassName
-<Switch thumbColorClassName="accent-white" trackColorOnClassName="accent-primary" />
-
-// placeholderTextColor → placeholderTextColorClassName
-<TextInput placeholderTextColorClassName="accent-gray-400 dark:accent-gray-600" />
-```
-
-**CRITICAL Rule**: `className` maps to the `style` prop — it handles layout, typography, backgrounds, borders, etc. But React Native has many color props that live OUTSIDE of `style` (like `color`, `tintColor`, `thumbColor`, `placeholderTextColor`). These require a separate `{propName}ClassName` prop with the `accent-` prefix. Without `accent-`, the class resolves to a style object — but these props expect a plain color string.
-
-```tsx
-// WRONG — className sets style, but ActivityIndicator's color is NOT a style prop
-<ActivityIndicator className="text-blue-500" />  // color will NOT be set
-
-// CORRECT — use the dedicated colorClassName prop with accent- prefix
-<ActivityIndicator colorClassName="accent-blue-500" />  // color IS set to #3b82f6
-
-// WRONG — tintColor is not a style prop on Image
-<Image className="tint-blue-500" source={icon} />  // won't work
-
-// CORRECT
-<Image tintColorClassName="accent-blue-500" source={icon} />
-```
-
-## Styling Third-Party Components
-
-### withUniwind (Recommended)
-
-Wrap once at module level, use with `className` everywhere:
-
-```tsx
-import { withUniwind } from 'uniwind';
-import { Image as ExpoImage } from 'expo-image';
-import { BlurView as RNBlurView } from 'expo-blur';
-import { LinearGradient as RNLinearGradient } from 'expo-linear-gradient';
-
-// Module-level wrapping (NEVER inside render functions)
-export const Image = withUniwind(ExpoImage);
-export const BlurView = withUniwind(RNBlurView);
-export const LinearGradient = withUniwind(RNLinearGradient);
-```
-
-`withUniwind` automatically maps:
-- `style` → `className`
-- `{name}Style` → `{name}ClassName`
-- `{name}Color` → `{name}ColorClassName` (with accent- prefix)
-
-For custom prop mappings:
-
-```tsx
-const StyledProgressBar = withUniwind(ProgressBar, {
-  width: {
-    fromClassName: 'widthClassName',
-    styleProperty: 'width',
-  },
-});
-```
-
-**Usage patterns:**
-
-- **Used in one file only** — define the wrapped component in that same file
-- **Used across multiple files** — wrap once in a shared module (e.g., `components/styled.ts`) and re-export
-
-```tsx
-// components/styled.ts
-import { withUniwind } from 'uniwind';
-import { Image as ExpoImage } from 'expo-image';
-export const Image = withUniwind(ExpoImage);
-
-// Then import everywhere:
-import { Image } from '@/components/styled';
-```
-
-**NEVER** call `withUniwind` on the same component in multiple files.
-
-**CRITICAL**: Do NOT use `withUniwind` on components from `react-native` or `react-native-reanimated`. These already have built-in `className` support:
-
-```tsx
-// WRONG — View already supports className natively
-const StyledView = withUniwind(View);        // DO NOT DO THIS
-const StyledText = withUniwind(Text);        // DO NOT DO THIS
-const StyledAnimatedView = withUniwind(Animated.View); // DO NOT DO THIS
-
-// CORRECT — only wrap third-party components
-const StyledExpoImage = withUniwind(ExpoImage);     // expo-image
-const StyledBlurView = withUniwind(BlurView);        // expo-blur
-const StyledMotiView = withUniwind(MotiView);        // moti
-```
-
-### useResolveClassNames
-
-Converts Tailwind class strings to React Native style objects. Use for one-off cases or components that only accept `style`:
-
-```tsx
-import { useResolveClassNames } from 'uniwind';
-
-const headerStyle = useResolveClassNames('bg-primary p-4');
-const cardStyle = useResolveClassNames('bg-card dark:bg-card rounded-lg shadow-sm');
-
-// React Navigation screen options
-<Stack.Navigator screenOptions={{ headerStyle, cardStyle }} />
-```
-
-### Comparison
-
-| Feature | withUniwind | useResolveClassNames |
-|---------|-------------|----------------------|
-| Setup | Once per component | Per usage |
-| Performance | Optimized | Slightly slower |
-| Best for | Reusable components | One-off, navigation config |
-| Syntax | `className="..."` | `style={...}` |
-
-## Dynamic ClassNames
-
-### NEVER do this (Tailwind scans at build time)
-
-```tsx
-// BROKEN — template literal with variable
-<View className={`bg-${color}-500`} />
-<Text className={`text-${size}`} />
-```
-
-### Correct patterns
-
-```tsx
-// Ternary with complete class names
-<View className={isActive ? 'bg-primary' : 'bg-muted'} />
-
-// Mapping object
-const colorMap = {
-  primary: 'bg-blue-500 text-white',
-  danger: 'bg-red-500 text-white',
-  ghost: 'bg-transparent text-foreground',
-};
-<Pressable className={colorMap[variant]} />
-
-// Array join for multiple conditions
-<View className={[
-  'p-4 rounded-lg',
-  isActive && 'bg-primary',
-  isDisabled && 'opacity-50',
-].filter(Boolean).join(' ')} />
-```
-
-## tailwind-variants (tv)
-
-For complex component styling with variants and compound variants:
-
-```tsx
-import { tv } from 'tailwind-variants';
-
-const button = tv({
-  base: 'font-semibold rounded-lg px-4 py-2 items-center justify-center',
-  variants: {
-    color: {
-      primary: 'bg-blue-500 text-white',
-      secondary: 'bg-gray-500 text-white',
-      danger: 'bg-red-500 text-white',
-      ghost: 'bg-transparent text-foreground border border-border',
-    },
-    size: {
-      sm: 'text-sm px-3 py-1.5',
-      md: 'text-base px-4 py-2',
-      lg: 'text-lg px-6 py-3',
-    },
-    disabled: {
-      true: 'opacity-50',
-    },
-  },
-  compoundVariants: [
-    { color: 'primary', size: 'lg', class: 'bg-blue-600' },
-  ],
-  defaultVariants: { color: 'primary', size: 'md' },
-});
-
-<Pressable className={button({ color: 'primary', size: 'lg' })}>
-  <Text className="text-white font-semibold">Click</Text>
-</Pressable>
-```
-
-## cn Utility — Class Deduplication
-
-Uniwind does **NOT** auto-deduplicate conflicting classNames. This means if the same property appears in multiple classes, **both will be applied and the result is unpredictable**. This is especially critical when mixing custom CSS classes with Tailwind utilities.
-
-### Setup
-
-```bash
-npm install tailwind-merge clsx
-```
-
-```ts
-// lib/cn.ts
-import { type ClassValue, clsx } from 'clsx';
-import { twMerge } from 'tailwind-merge';
-
-export function cn(...inputs: ClassValue[]) {
-  return twMerge(clsx(inputs));
-}
-```
-
-### When cn Is Required
-
-1. **Merging className props** — component accepts external className that may conflict:
-
-```tsx
-import { cn } from '@/lib/cn';
-
-<View className={cn('p-4 bg-white', props.className)} />
-<Text className={cn('text-base', isActive && 'text-primary', disabled && 'opacity-50')} />
-```
-
-2. **CRITICAL: Mixing custom CSS classes with Tailwind utilities** — if your custom CSS class sets a property that a Tailwind utility also sets, you MUST use `cn()` to deduplicate:
-
-```css
-/* global.css */
-.card {
-  background-color: white;
-  border-radius: 12px;
-  padding: 16px;
-}
-```
-
-```tsx
-// WRONG — both .card (padding: 16px) and p-6 (padding: 24px) apply, result is unpredictable
-<View className="card p-6" />
-
-// CORRECT — cn deduplicates, p-6 wins over .card's padding
-<View className={cn('card', 'p-6')} />
-```
-
-3. **tv() output combined with extra classes** — tv already handles its own variants, but if you add more classes on top:
-
-```tsx
-<Pressable className={cn(button({ color: 'primary' }), props.className)} />
-```
-
-### When cn Is NOT Needed
-
-- Static className with no conflicts: `<View className="flex-1 p-4 bg-white" />`
-- Single custom CSS class with no overlapping Tailwind: `<View className="card-shadow mt-4" />` (if card-shadow only sets box-shadow which no Tailwind class also sets)
-
-## Theming
-
-### Quick Setup (dark: prefix)
-
-Works immediately — no configuration needed:
-
-```tsx
-<View className="bg-white dark:bg-gray-900">
-  <Text className="text-black dark:text-white">Themed</Text>
-</View>
-```
-
-Best for small apps and prototyping. Does not scale to custom themes.
-
-### Scalable Setup (CSS Variables)
-
-Define in `global.css`, use everywhere without `dark:` prefix:
-
-```css
-@layer theme {
-  :root {
-    @variant light {
-      --color-background: #ffffff;
-      --color-foreground: #111827;
-      --color-foreground-secondary: #6b7280;
-      --color-card: #ffffff;
-      --color-border: #e5e7eb;
-      --color-muted: #9ca3af;
-      --color-primary: #3b82f6;
-      --color-danger: #ef4444;
-      --color-success: #10b981;
-    }
-    @variant dark {
-      --color-background: #000000;
-      --color-foreground: #ffffff;
-      --color-foreground-secondary: #9ca3af;
-      --color-card: #1f2937;
-      --color-border: #374151;
-      --color-muted: #6b7280;
-      --color-primary: #3b82f6;
-      --color-danger: #ef4444;
-      --color-success: #10b981;
-    }
-  }
-}
-```
-
-```tsx
-// Auto-adapts to current theme — no dark: prefix needed
-<View className="bg-card border border-border p-4 rounded-lg">
-  <Text className="text-foreground text-lg font-bold">Title</Text>
-  <Text className="text-muted mt-2">Subtitle</Text>
-</View>
-```
-
-Variable naming: `--color-background` → `bg-background`, `text-background`.
-
-**Prefer CSS variables over explicit `dark:` variants** — they're cleaner, maintain easier, and work with custom themes automatically.
-
-### Custom Themes
-
-**Step 1** — Define in `global.css`:
-
-```css
-@layer theme {
-  :root {
-    @variant light { /* ... */ }
-    @variant dark { /* ... */ }
-    @variant ocean {
-      --color-background: #0c4a6e;
-      --color-foreground: #e0f2fe;
-      --color-primary: #06b6d4;
-      --color-card: #0e7490;
-      --color-border: #155e75;
-      /* Must define ALL the same variables as light/dark */
-    }
-  }
-}
-```
-
-**Step 2** — Register in `metro.config.js` (exclude `light`/`dark` — they're automatic):
-
-```js
-module.exports = withUniwindConfig(config, {
-  cssEntryFile: './global.css',
-  extraThemes: ['ocean'],
-});
-```
-
-Restart Metro after adding themes.
-
-**Step 3** — Use:
-
-```tsx
-Uniwind.setTheme('ocean');
-```
-
-### Theme API
-
-```tsx
-import { Uniwind, useUniwind } from 'uniwind';
-
-// Imperative (no re-render)
-Uniwind.setTheme('dark');          // Force dark
-Uniwind.setTheme('light');         // Force light
-Uniwind.setTheme('system');        // Follow device (re-enables adaptive themes)
-Uniwind.setTheme('ocean');         // Custom theme (must be in extraThemes)
-Uniwind.currentTheme;              // Current theme name
-Uniwind.hasAdaptiveThemes;         // true if following system
-
-// Reactive hook (re-renders on change)
-const { theme, hasAdaptiveThemes } = useUniwind();
-```
-
-`Uniwind.setTheme('light')` / `setTheme('dark')` also calls `Appearance.setColorScheme` to sync native components (Alert, Modal, system dialogs).
-
-By default Uniwind uses "system" theme - follows device color scheme. If user wants to override it, just
-call Uniwind.setTheme with desired theme. It can be done above the React component to avoid theme switching at runtime.
-
-### Theme Switcher Example
-
-```tsx
-import { View, Pressable, Text, ScrollView } from 'react-native';
-import { Uniwind, useUniwind } from 'uniwind';
-
-export const ThemeSwitcher = () => {
-  const { theme, hasAdaptiveThemes } = useUniwind();
-  const activeTheme = hasAdaptiveThemes ? 'system' : theme;
-
-  const themes = [
-    { name: 'light', label: 'Light' },
-    { name: 'dark', label: 'Dark' },
-    { name: 'system', label: 'System' },
-  ];
-
-  return (
-    <ScrollView horizontal showsHorizontalScrollIndicator={false}>
-      <View className="flex-row gap-2 p-4">
-        {themes.map((t) => (
-          <Pressable
-            key={t.name}
-            onPress={() => Uniwind.setTheme(t.name)}
-            className={`px-4 py-3 rounded-lg items-center ${
-              activeTheme === t.name ? 'bg-primary' : 'bg-card border border-border'
-            }`}
-          >
-            <Text className={`text-sm ${
-              activeTheme === t.name ? 'text-white' : 'text-foreground'
-            }`}>
-              {t.label}
-            </Text>
-          </Pressable>
-        ))}
-      </View>
-    </ScrollView>
-  );
-};
-```
-
-### ScopedTheme
-
-Apply a different theme to a subtree without changing the global theme:
-
-```tsx
-import { ScopedTheme } from 'uniwind';
-
-<View className="gap-3">
-  <PreviewCard />
-
-  <ScopedTheme theme="dark">
-    <PreviewCard />  {/* Renders with dark theme */}
-  </ScopedTheme>
-
-  <ScopedTheme theme="ocean">
-    <PreviewCard />  {/* Renders with ocean theme */}
-  </ScopedTheme>
-</View>
-```
-
-- Nearest `ScopedTheme` wins (nested scopes supported)
-- Hooks (`useUniwind`, `useResolveClassNames`, `useCSSVariable`) resolve against the nearest scoped theme
-- `withUniwind`-wrapped components inside the scope also resolve scoped theme values
-- Custom themes require registration in `extraThemes`
-
-### useCSSVariable
-
-Access CSS variable values in JavaScript:
-
-```tsx
-import { useCSSVariable } from 'uniwind';
-
-const primaryColor = useCSSVariable('--color-primary');
-const spacing = useCSSVariable('--spacing-4');
-
-// Multiple variables at once
-const [bg, fg] = useCSSVariable(['--color-background', '--color-foreground']) as [string, string]
-```
-
-Use for: animations, chart libraries, third-party component configs, calculations with design tokens.
-
-It's required to cast the result of `useCSSVariable` as it can return: string | number | undefined.
-Uniwind doesn't know if given variable exist and what type it is, so it returns union type.
-
-### getCSSVariable
-
-Read CSS variable values outside of React (event handlers, async callbacks, utility modules, worklets). Available in Uniwind 1.6.4+.
-
-```ts
-import { Uniwind } from 'uniwind';
-
-const primary = Uniwind.getCSSVariable('--color-primary');
-const [bg, fg] = Uniwind.getCSSVariable(['--color-background', '--color-foreground']) as [string, string];
-```
-
-Same value rules as `useCSSVariable` (variable must be used in a `className` or declared in `@theme static`). Same return type: `string | number | undefined`. Cast as needed.
-
-Not reactive — value is read once. For reactive values inside components use `useCSSVariable`. Use `getCSSVariable` for one-shot reads (onPress handlers, utility functions, native module configs).
-
-### Runtime CSS Variable Updates
-
-Update theme variables at runtime (e.g., user-selected brand colors or API-driven themes):
-
-```tsx
-Uniwind.updateCSSVariables('light', {
-  '--color-primary': '#ff6600',
-  '--color-background': '#fafafa',
-});
-```
-
-Updates are theme-specific and take effect immediately.
-
-### @theme static
-
-For JS-only values not used in classNames:
-
-```css
-@theme static {
-  --chart-line-width: 2;
-  --chart-dot-radius: 4;
-  --animation-duration: 300;
-}
-```
-
-Access via `useCSSVariable('--chart-line-width')`. Use for: chart configs, animation durations, native module values.
-
-### OKLCH Colors support
-
-Perceptually uniform color format — wider gamut, consistent lightness:
-
-```css
-@layer theme {
-  :root {
-    @variant light {
-      --color-primary: oklch(0.5 0.2 240);
-      --color-background: oklch(1 0 0);
-    }
-    @variant dark {
-      --color-primary: oklch(0.6 0.2 240);
-      --color-background: oklch(0.13 0.004 17.69);
-    }
-  }
-}
-```
-
-### Display P3 Colors support
-
-Wide-gamut color format for devices that support the P3 color space (most modern iPhones and Macs). Uniwind parses `color(display-p3 ...)` values and converts them for native use:
-
-```css
-@layer theme {
-  :root {
-    @variant light {
-      --color-primary: color(display-p3 0.2 0.4 1);
-      --color-accent: color(display-p3 1 0.3 0.3);
-    }
-    @variant dark {
-      --color-primary: color(display-p3 0.3 0.5 1);
-      --color-accent: color(display-p3 1 0.4 0.4);
-    }
-  }
-}
-```
-
-## Platform Selectors
-
-Apply platform-specific styles directly in className:
-
-```tsx
-// Individual platforms
-<View className="ios:bg-red-500 android:bg-blue-500 web:bg-green-500" />
-
-// native: shorthand (iOS + Android)
-<View className="native:bg-blue-500 web:bg-gray-500" />
-
-// TV platforms
-<View className="tv:p-8 android-tv:bg-black apple-tv:bg-gray-900" />
-
-// Combine with other utilities
-<View className="p-4 ios:pt-12 android:pt-6 web:pt-4" />
-```
-
-Platform variants in `@layer theme` for global values (use `@variant`, not `@media`):
-
-```css
-@layer theme {
-  :root {
-    @variant ios { --font-sans: 'SF Pro Text'; }
-    @variant android { --font-sans: 'Roboto-Regular'; }
-    @variant web { --font-sans: 'Inter'; }
-  }
-}
-```
-
-**Prefer platform selectors over `Platform.select()`** — cleaner syntax, no imports needed.
-
-## Data Selectors
-
-Style based on prop values using `data-[prop=value]:utility`:
-
-```tsx
-// Boolean props
-<Pressable
-  data-selected={isSelected}
-  className="border rounded px-3 py-2 data-[selected=true]:ring-2 data-[selected=true]:ring-primary"
-/>
-
-// String props
-<View
-  data-state={isOpen ? 'open' : 'closed'}
-  className="p-4 data-[state=open]:bg-muted/50 data-[state=closed]:bg-transparent"
-/>
-
-// Tabs pattern
-<Pressable
-  data-selected={route.key === current}
-  className="px-4 py-2 rounded-md text-foreground/60
-    data-[selected=true]:bg-primary data-[selected=true]:text-white"
->
-  <Text>{route.title}</Text>
-</Pressable>
-
-// Toggle pattern
-<Pressable
-  data-checked={enabled}
-  className="h-6 w-10 rounded-full bg-muted data-[checked=true]:bg-primary"
->
-  <View className="h-5 w-5 rounded-full bg-background translate-x-0 data-[checked=true]:translate-x-4" />
-</Pressable>
-```
-
-**Rules**:
-- Only equality selectors supported (`data-[prop=value]`)
-- No presence-only selectors (`data-[prop]` — not supported)
-- No `has-data-*` parent selectors (not supported in React Native)
-- Booleans match both boolean and string forms
-
-## Interactive States
-
-```tsx
-// active: — when pressed
-<Pressable className="bg-primary active:bg-primary/80 active:opacity-90 active:scale-95">
-  <Text className="text-white">Press me</Text>
-</Pressable>
-
-// disabled: — when disabled prop is true
-<Pressable
-  disabled={isLoading}
-  className="bg-primary disabled:bg-gray-300 disabled:opacity-50"
->
-  <Text className="text-white disabled:text-gray-500">Submit</Text>
-</Pressable>
-
-// focus: — keyboard/accessibility focus
-<TextInput
-  className="border border-border rounded-lg px-4 py-2 focus:border-primary focus:ring-2 focus:ring-primary/20"
-/>
-
-<Pressable className="bg-card rounded-lg p-4 focus:ring-2 focus:ring-primary">
-  <Text className="text-foreground">Focusable</Text>
-</Pressable>
-```
-
-Components with state support:
-- **Pressable**: `active:`, `disabled:`, `focus:`
-- **TextInput**: `active:`, `disabled:`, `focus:`
-- **Switch**: `disabled:`
-- **Text**: `active:`, `disabled:`
-- **TouchableOpacity / TouchableHighlight / TouchableNativeFeedback / TouchableWithoutFeedback**: `active:`, `disabled:`
-
-## Responsive Breakpoints
-
-Mobile-first — unprefixed styles apply to all sizes, prefixed styles apply at that breakpoint and above:
-
-| Prefix | Min Width | Typical Device |
-|--------|-----------|----------------|
-| (none) | 0px | All (mobile) |
-| `sm:` | 640px | Large phones |
-| `md:` | 768px | Tablets |
-| `lg:` | 1024px | Landscape tablets |
-| `xl:` | 1280px | Desktops |
-| `2xl:` | 1536px | Large desktops |
-
-```tsx
-// Responsive padding and typography
-<View className="p-4 sm:p-6 lg:p-8">
-  <Text className="text-base sm:text-lg lg:text-xl font-bold">Responsive</Text>
-</View>
-
-// Responsive grid (1 col → 2 col → 3 col)
-<View className="flex-row flex-wrap">
-  <View className="w-full sm:w-1/2 lg:w-1/3 p-2">
-    <View className="bg-card p-4 rounded"><Text>Item</Text></View>
-  </View>
-</View>
-
-// Responsive visibility
-<View className="hidden sm:flex flex-row gap-4">
-  <Text>Visible on tablet+</Text>
-</View>
-<View className="flex sm:hidden">
-  <Text>Mobile only</Text>
-</View>
-```
-
-Custom breakpoints:
-
-```css
-@theme {
-  --breakpoint-xs: 480px;
-  --breakpoint-tablet: 820px;
-  --breakpoint-3xl: 1920px;
-}
-```
-
-Usage: `xs:p-2 tablet:p-4 3xl:p-8`
-
-**Design mobile-first** — start with base styles (no prefix), enhance with breakpoints:
-
-```tsx
-// CORRECT — mobile-first
-<View className="w-full sm:w-3/4 md:w-1/2 lg:w-1/3" />
-
-// WRONG — desktop-first (reversed order is confusing and fragile)
-<View className="w-full lg:w-1/2 md:w-3/4 sm:w-full" />
-```
-
-## Safe Area Utilities
-
-### Padding
-
-| Class | Description |
-|-------|-------------|
-| `p-safe` | All sides |
-| `pt-safe` / `pb-safe` / `pl-safe` / `pr-safe` | Individual sides |
-| `px-safe` / `py-safe` | Horizontal / vertical |
-
-### Margin
-
-| Class | Description |
-|-------|-------------|
-| `m-safe` | All sides |
-| `mt-safe` / `mb-safe` / `ml-safe` / `mr-safe` | Individual sides |
-| `mx-safe` / `my-safe` | Horizontal / vertical |
-
-### Positioning
-
-| Class | Description |
-|-------|-------------|
-| `inset-safe` | All sides |
-| `top-safe` / `bottom-safe` / `left-safe` / `right-safe` | Individual sides |
-| `x-safe` / `y-safe` | Horizontal / vertical inset |
-
-### Compound Variants
-
-| Pattern | Behavior | Example |
-|---------|----------|---------|
-| `{prop}-safe-or-{value}` | `Math.max(inset, value)` — ensures minimum spacing | `pt-safe-or-4` |
-| `{prop}-safe-offset-{value}` | `inset + value` — adds extra spacing on top of inset | `pb-safe-offset-4` |
-
-### Setup
-
-**Uniwind Free (default)** — requires `react-native-safe-area-context` to update insets.
-Wrap your App component in `SafeAreaProvider` and `SafeAreaListener` and call `Uniwind.updateInsets(insets)` in the `onChange` callback:
-
-```tsx
-import { SafeAreaProvider, SafeAreaListener } from 'react-native-safe-area-context';
-import { Uniwind } from 'uniwind';
-
-export default function App() {
-  return (
-    <SafeAreaProvider>
-      <SafeAreaListener
-        onChange={({ insets }) => {
-          Uniwind.updateInsets(insets);
-        }}
-      >
-        <View className="pt-safe px-safe">{/* content */}</View>
-      </SafeAreaListener>
-    </SafeAreaProvider>
-  );
-}
-```
-
-**Uniwind Pro** — automatic, no setup needed. Insets injected from native layer.
-
-## CSS Functions
-
-Uniwind provides CSS functions for device-aware and theme-aware styling. These can be used everywhere (custom CSS classes, `@utility`, etc.) — but NOT inside `@theme {}` (which only accepts static values). Use `@utility` to create reusable Tailwind-style utility classes:
-
-### hairlineWidth()
-
-Returns the thinnest line width displayable on the device. Use for subtle borders and dividers.
-
-```css
-@utility h-hairline { height: hairlineWidth(); }
-@utility border-hairline { border-width: hairlineWidth(); }
-@utility w-hairline { width: calc(hairlineWidth() * 10); }
-```
-
-```tsx
-<View className="h-hairline bg-gray-300" />
-<View className="border-hairline border-gray-200 rounded-lg p-4" />
-```
-
-### fontScale(multiplier?)
-
-Multiplies a base value by the device's font scale accessibility setting. Ensures text respects user preferences for larger or smaller text.
-
-- **`fontScale()`** — uses multiplier 1 (device font scale × 1)
-- **`fontScale(0.9)`** — smaller scale
-- **`fontScale(1.2)`** — larger scale
-
-```css
-@utility text-sm-scaled { font-size: fontScale(0.9); }
-@utility text-base-scaled { font-size: fontScale(); }
-@utility text-lg-scaled { font-size: fontScale(1.2); }
-```
-
-```tsx
-<Text className="text-sm-scaled text-gray-600">Small accessible text</Text>
-<Text className="text-base-scaled">Regular accessible text</Text>
-```
-
-### pixelRatio(multiplier?)
-
-Multiplies a value by the device's pixel ratio. Creates pixel-perfect designs that scale across screen densities.
-
-- **`pixelRatio()`** — uses multiplier 1 (device pixel ratio × 1)
-- **`pixelRatio(2)`** — double the pixel ratio
-
-```css
-@utility w-icon { width: pixelRatio(); }
-@utility w-avatar { width: pixelRatio(2); }
-```
-
-```tsx
-<Image source={{ uri: 'avatar.png' }} className="w-avatar rounded-full" />
-```
-
-### light-dark(lightValue, darkValue)
-
-Returns different values based on the current theme mode. Automatically adapts when the theme changes — no manual switching logic needed.
-
-- First parameter: value for light theme
-- Second parameter: value for dark theme
-
-```css
-@utility bg-adaptive { background-color: light-dark(#ffffff, #1f2937); }
-@utility text-adaptive { color: light-dark(#111827, #f9fafb); }
-@utility border-adaptive { border-color: light-dark(#e5e7eb, #374151); }
-```
-
-```tsx
-<View className="bg-adaptive border-adaptive border rounded-lg p-4">
-  <Text className="text-adaptive">Adapts to light/dark theme</Text>
-</View>
-```
-
-Also works in custom CSS classes (not just `@utility`):
-
-```css
-.adaptive-card {
-  background-color: light-dark(#ffffff, #1f2937);
-  color: light-dark(#111827, #f9fafb);
-}
-```
-
-## Custom CSS & Utilities
-
-### Custom CSS Classes
-
-Uniwind supports custom CSS class names defined in `global.css`. They are compiled at build time — no runtime overhead. Use them when you need styles that are hard to express as Tailwind utilities (e.g., complex box-shadow, multi-property bundles).
-
-```css
-/* global.css */
-.card-shadow {
-  background-color: white;
-  border-radius: 12px;
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
-}
-
-.adaptive-surface {
-  background-color: light-dark(#ffffff, #1f2937);
-  color: light-dark(#111827, #f9fafb);
-}
-
-.container {
-  flex: 1;
-  width: 100%;
-  max-width: 1200px;
-}
-```
-
-Apply via `className` just like any Tailwind class:
-
-```tsx
-<View className="card-shadow" />
-```
-
-### Mixing Custom CSS with Tailwind
-
-You can combine custom CSS classes with Tailwind utilities in a single `className`:
-
-```tsx
-<View className="card-shadow p-4 m-2">
-  <Text className="adaptive-surface mb-2">{title}</Text>
-  <View className="container flex-row">{children}</View>
-</View>
-```
-
-**WARNING**: If a custom CSS class and a Tailwind utility set the **same property**, you **MUST** use `cn()` to deduplicate. Without `cn()`, both values apply and the result is unpredictable:
-
-```tsx
-// WRONG — .container sets flex:1, and flex-1 also sets flex:1 (harmless but wasteful)
-// WRONG — .container sets width:100%, and w-full also sets width:100% (redundant)
-// DANGEROUS — .card-shadow sets border-radius:12px, and rounded-2xl sets border-radius:16px — CONFLICT!
-<View className="card-shadow rounded-2xl" />
-
-// CORRECT — cn ensures rounded-2xl wins
-import { cn } from '@/lib/cn';
-<View className={cn('card-shadow', 'rounded-2xl')} />
-```
-
-**Rule of thumb**: If your custom CSS class sets properties that might overlap with Tailwind utilities you'll also use, always wrap with `cn()`. See **cn Utility** section for full setup.
-
-### Guidelines for Custom CSS
-
-- Keep selectors flat — no deep nesting or child selectors
-- Prefer Tailwind utilities for simple, single-property styles
-- Use custom classes for complex or multi-property bundles that would be verbose in className
-- Use `light-dark()` for theme-aware custom classes
-- Custom classes are great for shared design tokens that don't fit Tailwind's naming (e.g., `.card`, `.chip`, `.badge-dot`)
-
-### Custom Utilities (@utility)
-
-The `@utility` directive creates utility classes that work exactly like built-in Tailwind classes. Three main use cases:
-
-#### 1. Variable-driven utilities (runtime-injected values)
-
-Create a utility whose value comes from a CSS variable injected at runtime via `updateCSSVariables`. Use `@theme static` to declare the variable so Uniwind tracks it even before it is updated:
-
-```css
-/* global.css */
-@theme static {
-  --header-height: 0px;
-}
-
-@utility p-safe-header {
-  padding-top: var(--header-height);
-}
-```
-
-Inject the real value at runtime (e.g., from react-navigation's layout event):
-
-```tsx
-import { Uniwind } from 'uniwind'
-
-// e.g., inside a navigation layout listener
-Uniwind.updateCSSVariables(Uniwind.currentTheme, {
-  '--header-height': headerHeight,
-})
-```
-
-```tsx
-<View className="p-safe-header flex-1" />
-```
-
-#### 2. Brand-new utilities (no Tailwind equivalent)
-
-For styles that have no built-in Tailwind class:
-
-```css
-@utility h-hairline { height: hairlineWidth(); }
-@utility text-scaled { font-size: fontScale(); }
-@utility card-shadow {
-  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
-}
-```
-
-Usage like any Tailwind class: `<View className="h-hairline card-shadow" />`
-
-#### 3. Overriding existing Tailwind utilities
-
-Use `@utility` to completely replace what a built-in class does. Example: make `border` always use `--color-primary`:
-
-```css
-@utility border {
-  border-width: 1px;
-  border-style: solid;
-  border-color: var(--color-primary);
-}
-```
-
-## @theme Directive
-
-Customize Tailwind design tokens in `global.css`:
-
-```css
-@theme {
-  /* Colors */
-  --color-primary: #3b82f6;
-  --color-brand-500: #3b82f6;
-  --color-brand-900: #1e3a8a;
-
-  /* Typography */
-  --font-sans: 'Roboto-Regular';
-  --font-sans-medium: 'Roboto-Medium';
-  --font-sans-bold: 'Roboto-Bold';
-  --font-mono: 'FiraCode-Regular';
-
-  /* Spacing & sizing */
-  --text-base: 15px;
-  --spacing-4: 16px;
-  --radius-lg: 12px;
-
-  /* Breakpoints */
-  --breakpoint-tablet: 820px;
-}
-```
-
-Usage: `bg-brand-500`, `text-brand-900`, `font-sans`, `font-mono`, `rounded-lg`.
-
-## Fonts
-
-React Native requires a **single font** per family — no fallbacks:
-
-```css
-@theme {
-  --font-sans: 'Roboto-Regular';
-  --font-sans-bold: 'Roboto-Bold';
-  --font-mono: 'FiraCode-Regular';
-}
-```
-
-Font name must **exactly match** the font file name (without extension).
-
-**Expo**: Configure fonts in `app.json` with the `expo-font` plugin, then reference in CSS.
-
-**Bare RN**: Use `react-native-asset` to link fonts, same CSS config.
-
-**Platform-specific fonts** (use `@variant`, not `@media`):
-
-```css
-@layer theme {
-  :root {
-    @variant ios { --font-sans: 'SF Pro Text'; }
-    @variant android { --font-sans: 'Roboto-Regular'; }
-    @variant web { --font-sans: 'system-ui'; }
-  }
-}
-```
-
-## Gradients
-
-Built-in support — no extra dependencies:
-
-```tsx
-<View className="bg-gradient-to-r from-red-500 via-yellow-500 to-green-500 p-4 rounded-lg">
-  <Text className="text-white font-bold">Gradient</Text>
-</View>
-```
-
-For `expo-linear-gradient`, you can wrap it with `withUniwind` for className-based layout and styling (padding, border-radius, flex, etc.), but the `colors` prop is an array that cannot be resolved via className — it must be provided explicitly. Use `useCSSVariable` to get theme-aware colors:
-
-```tsx
-import { useCSSVariable } from 'uniwind';
-import { withUniwind } from 'uniwind';
-import { LinearGradient as RNLinearGradient } from 'expo-linear-gradient';
-
-const LinearGradient = withUniwind(RNLinearGradient);
-
-function GradientCard() {
-  // useCSSVariable returns string | number | undefined
-  const primary = useCSSVariable('--color-primary');
-  const secondary = useCSSVariable('--color-secondary');
-
-  // Guard against undefined — LinearGradient.colors requires valid ColorValues
-  if (!primary || !secondary) {
-    return null;
-  }
-
-  return (
-    <LinearGradient
-      className="flex-1 rounded-2xl p-6"
-      colors={[primary as string, secondary as string]}
-    >
-      <Text className="text-white font-bold">Themed gradient</Text>
-    </LinearGradient>
-  );
-}
-```
-
-Alternatively, export a wrapped component from a shared module for reuse:
-
-```tsx
-// components/styled.ts
-import { withUniwind } from 'uniwind';
-import { LinearGradient as RNLinearGradient } from 'expo-linear-gradient';
-
-export const LinearGradient = withUniwind(RNLinearGradient);
-```
-
-```tsx
-// usage — className handles layout, colors still passed manually
-import { LinearGradient } from '@/components/styled';
-
-<LinearGradient className="rounded-xl p-4" colors={['#ff6b6b', '#4ecdc4']}>
-  <Text className="text-white">Static gradient</Text>
-</LinearGradient>
-```
-
-## React Navigation Integration
-
-Use `useResolveClassNames` for screen options that only accept `style` objects:
-
-```tsx
-import { useResolveClassNames } from 'uniwind';
-
-function Layout() {
-  const headerStyle = useResolveClassNames('bg-background');
-  const headerTitleStyle = useResolveClassNames('text-foreground font-bold');
-
-  return (
-    <Stack.Navigator
-      screenOptions={{
-        headerStyle,
-        headerTitleStyle,
-      }}
-    />
-  );
-}
-```
-
-Keep React Navigation's `<ThemeProvider>` if already in use — it manages navigation-specific theming.
-
-## UI Kit Compatibility
-
-- **HeroUI Native**: Works with Uniwind. Uses `tailwind-variants` (tv) internally. Apply `className` directly on HeroUI components. **Bun users**: Bun uses symlinks for `node_modules`, which can cause Tailwind's Oxide scanner to miss library classes in production builds. Fix: use the resolved path in `@source` and hoist the package:
-  ```css
-  @source "../../node_modules/heroui-native/lib";
-  ```
-  ```ini
-  # .npmrc
-  public-hoist-pattern[]=heroui-native
-  ```
-- **react-native-reusables**: Compatible.
-- **Gluestack v4.1+**: Compatible.
-- **Lucide React Native**: Use `withUniwind(LucideIcon)` with `colorClassName="accent-blue-500"` for icon color. Works for all Lucide icons.
-- **@shopify/flash-list**: Use `withUniwind(FlashList)` for `className` and `contentContainerClassName` support. Note: `withUniwind` loses generic type params on `ref` — cast manually if needed.
-
-Use semantic color tokens (`bg-primary`, `text-foreground`) for theme consistency across UI kits.
-
-## Supported vs Unsupported Classes
-
-React Native uses the Yoga layout engine. Key differences from web CSS:
-- **No CSS cascade/inheritance** — styles don't inherit from parents
-- **Flexbox by default** — all views use flexbox with `flexDirection: 'column'`
-- **Limited CSS properties** — no floats, grid, pseudo-elements
-
-### Built-in Extra Utilities
-
-Uniwind provides additional utility classes for React Native features not covered by standard Tailwind:
-
-| Class | Effect |
-|-------|--------|
-| `border-continuous` | Sets `borderCurve: 'continuous'` — smooth, superellipse corners (iOS) |
-| `border-circular` | Sets `borderCurve: 'circular'` — standard circular corners (iOS default) |
-
-```tsx
-// Smooth iOS-style rounded corners (like SwiftUI's .continuous)
-<View className="rounded-2xl border-continuous bg-card p-4">
-  <Text className="text-foreground">Smooth corners</Text>
-</View>
-```
-
-### Supported (all standard Tailwind)
-
-Layout, spacing, sizing, typography, colors, borders, effects, flexbox, positioning, transforms, interactive states.
-
-### Unsupported (web-specific, silently ignored)
-
-- `hover:` `visited:` — use Pressable `active:` instead
-- `before:` `after:` `placeholder:` — pseudo-elements
-- `float-*` `clear-*` `columns-*`
-- `print:` `screen:`
-- `break-before-*` `break-after-*` `break-inside-*`
-
-## Uniwind Pro
-
-Paid upgrade with 100% API compatibility. Built on a 2nd-generation C++ engine for apps that demand the best performance. Graduated pricing (billed annually): **$99/seat** (1-3), **$49** (4-6), **$29** (7-15), **$1** (16+). Pricing and licensing: [https://uniwind.dev/pricing](https://uniwind.dev/pricing)
-
-### Pricing & Licensing
-
-- **Graduated per-seat pricing** (billed annually, VAT excluded unless applicable): $99 for seats 1-3, $49 for 4-6, $29 for 7-15, $1 for 16+
-- **Individual License**: Personal Pro license per engineer
-- **Team License**: Single key management — add or remove members instantly
-- **CI/CD License**: Full support for automated and headless build environments
-- **Enterprise**: Custom plans available (contact support@uniwind.dev)
-- **Priority Support**: Critical issues resolved with priority response times
-
-### Overview
-
-- **C++ style engine**: Forged on the 2nd-gen Unistyles C++ engine. Injects styles directly into the ShadowTree without triggering React re-renders — a direct, optimized highway between classNames and the native layer
-- **Performance**: Benchmarked at ~55ms (vs StyleSheet 49ms, traditional Uniwind 81ms, NativeWind 197ms) — near-native speed
-- **55+ className props** update without re-renders across 20 components (all component bindings listed above)
-- **Reanimated animations**: `animate-*` and `transition-*` via className (Reanimated v4)
-- **Native insets & runtime values**: Automatic safe area injection, device rotation, and font size updates — no `SafeAreaListener` setup needed
-- **Theme transitions**: Native animated transitions when switching themes (fade, slide, circle mask)
-- **Group variants**: Tailwind `group-active:*` / `group-focus:*` propagate parent interaction state through the C++ shadow tree with zero re-renders
-- **Priority support**: Don't let technical hurdles slow your team down
-
-Package: `"uniwind": "npm:uniwind-pro@latest"` in `package.json`.
-
-### Installation
-
-1. Set dependency alias in `package.json`:
-   ```json
-   { "dependencies": { "uniwind": "npm:uniwind-pro@latest" } }
-   ```
-
-2. Install peer dependencies:
-   ```bash
-   npm install react-native-nitro-modules react-native-reanimated react-native-worklets
-   ```
-
-3. Authenticate: `npx uniwind-pro` (interactive — select "Login with GitHub")
-
-4. Add Babel plugin:
-   ```js
-   // babel.config.js
-   module.exports = {
-     presets: ['module:metro-react-native-babel-preset'],
-     plugins: ['react-native-worklets/plugin'],
-   };
-   ```
-
-5. Whitelist postinstall if needed:
-   - **bun**: Add `"trustedDependencies": ["uniwind"]` to `package.json`
-   - **yarn v2+**: Configure in `.yarnrc.yml`
-   - **pnpm**: `pnpm config set enable-pre-post-scripts true`
-
-6. Rebuild native app:
-   ```bash
-   npx expo prebuild --clean && npx expo run:ios
-   ```
-
-Pro does **NOT** work with Expo Go. Requires native rebuild.
-
-**Verify installation**: Check for native modules (`.cpp`, `.mm` files) in `node_modules/uniwind`.
-
-### Reanimated Animations (Requires Reanimated v4.0.0+)
-
-```tsx
-<View className="size-32 bg-primary rounded animate-spin" />
-<View className="size-32 bg-primary rounded animate-bounce" />
-<View className="size-32 bg-primary rounded animate-pulse" />
-<View className="size-32 bg-primary rounded animate-ping" />
-
-// Loading spinner
-<View className="size-8 border-4 border-gray-200 border-t-blue-500 rounded-full animate-spin" />
-```
-
-Custom keyframe animations beyond Tailwind defaults:
-
-| Class | Description |
-|-------|-------------|
-| `animate-wiggle` | Rotational wiggle |
-| `animate-shake` | Horizontal shake |
-| `animate-flash` | Opacity flash on/off |
-| `animate-rubber-band` | Elastic scale stretch |
-| `animate-swing` | Pendulum swing |
-| `animate-tada` | Scale + rotate attention seeker |
-| `animate-heartbeat` | Double-pulse heartbeat |
-| `animate-jello` | Rotational jello wobble |
-| `animate-float` | Gentle vertical float |
-| `animate-breathe` | Subtle breathing scale |
-| `animate-tilt` | Alternating tilt rotation |
-| `animate-glitch` | Rapid horizontal jitter |
-
-Components auto-swap to Animated versions when animation classes detected:
-
-| Component | Animated Version |
-|-----------|------------------|
-| `View` | `Animated.View` |
-| `Text` | `Animated.Text` |
-| `Image` | `Animated.Image` |
-| `ImageBackground` | `Animated.ImageBackground` |
-| `ScrollView` | `Animated.ScrollView` |
-| `FlatList` | `Animated.FlatList` |
-| `TextInput` | `Animated.TextInput` |
-| `Pressable` | `Animated.Pressable` |
-
-### Entering & Exiting Animations
-
-Drive Reanimated's entering/exiting animations via className — no Reanimated imports needed. Components auto-upgrade when `uw-*` classes are detected.
-
-```tsx
-// Bounce in, bounce out
-{visible && <View className="size-20 bg-primary rounded-xl uw-entering-bounce-in uw-exiting-bounce-out" />}
-
-// Fade in slowly (1000ms)
-{visible && <View className="size-20 bg-primary rounded-xl uw-entering-fade-in uw-entering-duration-1000 uw-exiting-fade-out" />}
-```
-
-**Entering presets**: `uw-entering-fade-in` `uw-entering-fade-in-right` `uw-entering-fade-in-left` `uw-entering-fade-in-up` `uw-entering-fade-in-down` `uw-entering-slide-in-right` `uw-entering-slide-in-left` `uw-entering-slide-in-up` `uw-entering-slide-in-down` `uw-entering-zoom-in` `uw-entering-zoom-in-rotate` `uw-entering-zoom-in-left` `uw-entering-zoom-in-right` `uw-entering-zoom-in-up` `uw-entering-zoom-in-down` `uw-entering-zoom-in-easy-up` `uw-entering-zoom-in-easy-down` `uw-entering-bounce-in` `uw-entering-bounce-in-down` `uw-entering-bounce-in-up` `uw-entering-bounce-in-left` `uw-entering-bounce-in-right` `uw-entering-flip-in-x-up` `uw-entering-flip-in-x-down` `uw-entering-flip-in-y-left` `uw-entering-flip-in-y-right` `uw-entering-flip-in-easy-x` `uw-entering-flip-in-easy-y` `uw-entering-stretch-in-x` `uw-entering-stretch-in-y` `uw-entering-rotate-in-down-left` `uw-entering-rotate-in-down-right` `uw-entering-rotate-in-up-left` `uw-entering-rotate-in-up-right` `uw-entering-roll-in-left` `uw-entering-roll-in-right` `uw-entering-pinwheel-in` `uw-entering-light-speed-in-right` `uw-entering-light-speed-in-left`
-
-**Exiting presets**: `uw-exiting-fade-out` `uw-exiting-fade-out-right` `uw-exiting-fade-out-left` `uw-exiting-fade-out-up` `uw-exiting-fade-out-down` `uw-exiting-slide-out-right` `uw-exiting-slide-out-left` `uw-exiting-slide-out-up` `uw-exiting-slide-out-down` `uw-exiting-zoom-out` `uw-exiting-zoom-out-rotate` `uw-exiting-zoom-out-left` `uw-exiting-zoom-out-right` `uw-exiting-zoom-out-up` `uw-exiting-zoom-out-down` `uw-exiting-zoom-out-easy-up` `uw-exiting-zoom-out-easy-down` `uw-exiting-bounce-out` `uw-exiting-bounce-out-down` `uw-exiting-bounce-out-up` `uw-exiting-bounce-out-left` `uw-exiting-bounce-out-right` `uw-exiting-flip-out-x-up` `uw-exiting-flip-out-x-down` `uw-exiting-flip-out-y-left` `uw-exiting-flip-out-y-right` `uw-exiting-flip-out-easy-x` `uw-exiting-flip-out-easy-y` `uw-exiting-stretch-out-x` `uw-exiting-stretch-out-y` `uw-exiting-rotate-out-down-left` `uw-exiting-rotate-out-down-right` `uw-exiting-rotate-out-up-left` `uw-exiting-rotate-out-up-right` `uw-exiting-roll-out-left` `uw-exiting-roll-out-right` `uw-exiting-pinwheel-out` `uw-exiting-light-speed-out-right` `uw-exiting-light-speed-out-left`
-
-**Animation modifiers** (pattern: `uw-{entering|exiting|layout}-{modifier}`):
-- Duration: `uw-{type}-duration-75` `uw-{type}-duration-100` ... `uw-{type}-duration-1000` or arbitrary `uw-{type}-duration-{ms}`
-- Delay: `uw-{type}-delay-75` ... `uw-{type}-delay-1000` or arbitrary `uw-{type}-delay-{ms}`
-- Easing: `uw-{type}-ease-linear` `uw-{type}-ease-in` `uw-{type}-ease-out` `uw-{type}-ease-in-out` `uw-{type}-ease-bounce`
-- Spring: `uw-{type}-springify` `uw-{type}-damping-{value}` `uw-{type}-stiffness-{value}` `uw-{type}-mass-{value}`
-
-### Layout Transitions
-
-Animate position/size changes when siblings are added or removed:
-
-```tsx
-<View className="w-full gap-2">
-  {items.map(item => (
-    <View key={item.id} className={`h-14 ${item.color} rounded-xl uw-entering-fade-in uw-exiting-fade-out uw-layout-linear-transition`} />
-  ))}
-</View>
-```
-
-| Class | Description |
-|-------|-------------|
-| `uw-layout-linear-transition` | Smooth linear repositioning |
-| `uw-layout-fading-transition` | Fade during repositioning |
-| `uw-layout-jumping-transition` | Bouncy jump to new position |
-| `uw-layout-curved-transition` | Curved path repositioning |
-| `uw-layout-sequenced-transition` | Sequenced repositioning |
-| `uw-layout-entry-exit-transition` | Combined entry/exit during layout |
-
-### Transitions
-
-Smooth property changes when className or state changes:
-
-```tsx
-// Color transition on press
-<Pressable className="bg-primary active:bg-red-500 transition-colors duration-300" />
-
-// Opacity transition
-<View className={`transition-opacity duration-500 ${visible ? 'opacity-100' : 'opacity-0'}`} />
-
-// Transform transition
-<Pressable className="active:scale-95 transition-transform duration-150" />
-
-// All properties
-<Pressable className="bg-primary px-6 py-3 rounded-lg active:scale-95 active:bg-primary/80 transition-all duration-150">
-  <Text className="text-white font-semibold">Animated Button</Text>
-</Pressable>
-```
-
-| Class | Properties |
-|-------|------------|
-| `transition-none` | No transition |
-| `transition-all` | All properties |
-| `transition-colors` | Background, border, text colors |
-| `transition-opacity` | Opacity |
-| `transition-shadow` | Box shadow |
-| `transition-transform` | Scale, rotate, translate |
-
-Duration: `duration-75` `duration-100` `duration-150` `duration-200` `duration-300` `duration-500` `duration-700` `duration-1000`
-
-Easing: `ease-linear` `ease-in` `ease-out` `ease-in-out`
-
-Delay: `delay-75` `delay-100` `delay-150` `delay-200` `delay-300` `delay-500` `delay-700` `delay-1000`
-
-### Using Reanimated Directly
-
-Still works with Uniwind classes:
-
-```tsx
-import Animated, { FadeIn, FlipInXUp, LinearTransition } from 'react-native-reanimated';
-
-<Animated.Text entering={FadeIn.delay(500)} className="text-foreground text-lg">
-  Fading in
-</Animated.Text>
-
-<Animated.FlatList
-  data={data}
-  className="flex-none"
-  contentContainerClassName="px-2"
-  layout={LinearTransition}
-  renderItem={({ item }) => (
-    <Animated.Text entering={FlipInXUp} className="text-foreground py-2">
-      {item}
-    </Animated.Text>
-  )}
-/>
-```
-
-### Shadow Tree Updates
-
-No code changes needed — props connect directly to C++ engine, eliminating re-renders automatically.
-
-### Group Variants
-
-Tailwind `group` variants propagate parent interaction state to descendants through the C++ shadow tree. No re-renders, no context providers.
-
-```tsx
-// Basic group — descendants react to parent press
-<Pressable className="group p-4 bg-base rounded-xl">
-  <Text className="text-default group-active:text-primary">Press the card</Text>
-  <View className="size-8 bg-blue-500 rounded group-active:bg-red-500" />
-</Pressable>
-
-// Named groups — descendants pick which ancestor to follow
-<Pressable className="group/card p-4 bg-base rounded-xl">
-  <Pressable className="group/button px-3 py-1 bg-primary rounded">
-    <Text className="text-white group-active/card:opacity-50 group-active/button:font-bold">
-      Nested groups
-    </Text>
-  </Pressable>
-</Pressable>
-```
-
-**Supported variants**: `group-active:*` (press), `group-focus:*` (focus). Named variants: `group-active/{name}:*`, `group-focus/{name}:*`.
-
-**Supported group parents**: `Pressable` (press + focus), `Text` (press — requires `onPress`, even empty). `TouchableOpacity`, `TouchableHighlight`, `TouchableWithoutFeedback`, and `TextInput` do **not** act as group parents — wrap in a `Pressable` marked `group`.
-
-**Not supported**: `group-hover:*` (no pointer hover on native), `group-disabled:*` (parsed but no shadow tree trigger), arbitrary `group-[.selector]:*` variants, implicit `in-*` variants.
-
-### Suspense Support
-
-Components inside React `Suspense` boundaries are handled correctly. While a subtree is suspended, Uniwind keeps the C++ shadow entries alive so theme updates and runtime changes (dark mode, orientation, etc.) still reach suspended nodes. When the tree unsuspends, styles are already up to date — no flash of stale theme.
-
-### Native Insets
-
-Remove `SafeAreaListener` setup — insets injected from native layer:
-
-```tsx
-// With Pro — just use safe area classes directly
-<View className="pt-safe pb-safe">{/* content */}</View>
-```
-
-### Theme Transitions (Pro)
-
-Native animated transitions when switching themes. Supported on iOS, Android, and Web.
-
-```tsx
-import { Uniwind, ThemeTransitionPreset } from 'uniwind';
-
-// Fade transition
-Uniwind.setTheme('dark', { preset: ThemeTransitionPreset.Fade });
-
-// Slide transitions
-Uniwind.setTheme('dark', { preset: ThemeTransitionPreset.SlideRightToLeft });
-Uniwind.setTheme('light', { preset: ThemeTransitionPreset.SlideLeftToRight });
-
-// Circle mask transitions (expand from a corner or center)
-Uniwind.setTheme('ocean', { preset: ThemeTransitionPreset.CircleCenter });
-
-// Blur transitions
-Uniwind.setTheme('dark', { preset: ThemeTransitionPreset.Blur });
-Uniwind.setTheme('dark', { preset: ThemeTransitionPreset.BlurRightToLeft });
-
-// No animation
-Uniwind.setTheme('light');
-```
-
-Available presets:
-
-| Preset | Effect |
-|--------|--------|
-| `ThemeTransitionPreset.None` | Instant switch, no animation |
-| `ThemeTransitionPreset.Fade` | Crossfade between themes |
-| `ThemeTransitionPreset.SlideRightToLeft` | Slide new theme in from right |
-| `ThemeTransitionPreset.SlideLeftToRight` | Slide new theme in from left |
-| `ThemeTransitionPreset.CircleTopRight` | Circle mask expanding from top-right |
-| `ThemeTransitionPreset.CircleTopLeft` | Circle mask expanding from top-left |
-| `ThemeTransitionPreset.CircleBottomRight` | Circle mask expanding from bottom-right |
-| `ThemeTransitionPreset.CircleBottomLeft` | Circle mask expanding from bottom-left |
-| `ThemeTransitionPreset.CircleCenter` | Circle mask expanding from center |
-| `ThemeTransitionPreset.Blur` | Blur out animation |
-| `ThemeTransitionPreset.BlurRightToLeft` | Directional blur from right to left |
-| `ThemeTransitionPreset.BlurLeftToRight` | Directional blur from left to right |
-
-## Setup Diagnostics
-
-When styles aren't working, check in this order:
-
-### 1. package.json
-- `"uniwind"` (or `"uniwind-pro"`) in dependencies
-- `"tailwindcss"` at v4+ (`^4.0.0`)
-- For Pro: `react-native-nitro-modules`, `react-native-reanimated`, `react-native-worklets`
-
-### 2. metro.config.js
-- `withUniwindConfig` imported from `'uniwind/metro'`
-- `withUniwindConfig` is the **outermost** wrapper
-- `cssEntryFile` is a **relative path string** (e.g., `'./global.css'`)
-- No `path.resolve()` or absolute paths
-
-### 3. global.css
-- Contains `@import 'tailwindcss';` AND `@import 'uniwind';`
-- Imported in `App.tsx` or root layout, **NOT** in `index.ts`/`index.js`
-- Location determines app root for Tailwind scanning
-
-### 4. babel.config.js (Pro only)
-- `'react-native-worklets/plugin'` in plugins array
-
-### 5. TypeScript
-- `uniwind-types.d.ts` exists (generated after running Metro)
-- Included in `tsconfig.json` or placed in `src/`/`app/` dir
-
-### 6. Build
-- Metro server restarted after config changes
-- Metro cache cleared (`npx expo start --clear` or `npx react-native start --reset-cache`)
-- Native rebuild done (if Pro or after dependency changes)
-
-## Troubleshooting
-
-| Symptom | Cause | Fix |
-|---------|-------|-----|
-| Styles not applying | Missing imports in global.css | Add `@import 'tailwindcss'; @import 'uniwind';` |
-| Styles not applying | global.css imported in index.js | Move import to App.tsx or `_layout.tsx` |
-| Classes not detected | global.css in nested dir, components elsewhere | Add `@source '../components'` in global.css |
-| TypeScript errors on className | Missing types file | Run Metro to generate `uniwind-types.d.ts` |
-| `withUniwindConfig is not a function` | Wrong import | Use `require('uniwind/metro')` not `require('uniwind')` |
-| Hot reload full-reloads | global.css imported in wrong file | Move to App.tsx or root layout |
-| `cssEntryFile` error / Metro crash | Absolute path used | Use relative: `'./global.css'` |
-| `withUniwindConfig` not outermost | Another wrapper wraps Uniwind | Swap order so Uniwind is outermost |
-| Dark theme not working | Missing `@variant dark` | Define dark variant in `@layer theme` |
-| Custom theme not appearing | Not registered in metro config | Add to `extraThemes` array, restart Metro |
-| Fonts not loading | Font name mismatch | CSS font name must match file name exactly (no extension) |
-| `rem` values too large/small | Wrong base rem | Set `polyfills: { rem: 14 }` for NativeWind compat |
-| Unsupported CSS warning | Web-specific CSS used | Enable `debug: true` to identify; remove unsupported properties |
-| `Failed to serialize javascript object` | Complex CSS, circular refs, or stale cache | Clear caches: `watchman watch-del-all; rm -rf node_modules/.cache; npx expo start --clear`. Also check if docs/markdown files containing CSS classes are in the scan path (see below) |
-| `Failed to serialize javascript object` from llms-full.txt or docs | Docs/markdown files with CSS classes in project dir get scanned by Tailwind | Move `.md` files with CSS examples outside the project root, or add to `.gitignore` so Tailwind's scanner skips them |
-| `unstable_enablePackageExports` conflict | App disables package exports | Use selective resolver for Uniwind and culori |
-| Classes from monorepo package missing | Not included in Tailwind scan | Add `@source '../../packages/ui'` in global.css |
-| Classes from `node_modules` library missing in production (bun) | Bun uses symlinks; Tailwind's Oxide scanner can't follow them | Use resolved path: `@source "../../node_modules/heroui-native/lib"` and add `public-hoist-pattern[]=heroui-native` to `.npmrc` |
-| `active:` not working with `withUniwind` | `withUniwind` does NOT support interactive state selectors | Only core RN `Pressable`/`TextInput`/`Switch` support `active:`/`focus:`/`disabled:`. Third-party pressables wrapped with `withUniwind` won't get states |
-| `withUniwind` custom mapping overrides `className`+`style` merging | When manual mapping is provided, `style` prop is not merged | Use auto mapping (no second arg) for `className`+`style` merge. For manual mapping + `className`, double-wrap: `withUniwind(withUniwind(Comp), { mapping })` |
-| `withUniwind` loses generic types on `ref` (e.g., `FlashList<T>`) | TypeScript limitation with HOCs | Cast the ref manually: `ref={scrollRef as any}` |
-| Platform-specific fonts: `@theme` block error | `@media ios/android` inside `@theme {}` | Use `@layer theme { :root { @variant ios { ... } } }` instead — `@theme` only accepts custom properties, and platform selection uses `@variant` not `@media` |
-| `Uniwind.setTheme('system')` crash on Android (RN 0.82+) | RN 0.82 changed Appearance API | Update to latest Uniwind (fixed). Avoid `setTheme('system')` on older Uniwind + RN 0.82+ |
-| Styles flash/disappear on initial load (Android) | `SafeAreaListener` fires before component listeners mount | Fixed in recent versions. If persists, ensure Uniwind is latest |
-| `useTVEventHandler` is undefined | Uniwind module replacement interferes with tvOS exports | Fixed in v1.2.1+. Update Uniwind |
-| `@layer theme` variables not rendering on web | Bug with RNW + Expo SDK 55 | Fixed in v1.4.1+. Update Uniwind |
-| `updateCSSVariables` wrong theme at app start | Calling for multiple themes back-to-back; last call wins on first render | Call `updateCSSVariables` for the current theme last. After initial load, order doesn't matter |
-| Pro: animations not working | Missing Babel plugin | Add `react-native-worklets/plugin` to babel.config.js |
-| Pro: module not found | No native rebuild | Run `npx expo prebuild --clean` then `npx expo run:ios` |
-| Pro: postinstall failed | Package manager blocks scripts | Add to `trustedDependencies` (bun) or configure yarn/pnpm |
-| Pro: auth expired | Login session expired (180-day lifetime) | Run `npx uniwind-pro`, re-login |
-| Pro: download limit reached | Monthly download limit hit | Check Pro dashboard, limits reset monthly |
-| Pro: `Uniwind.updateInsets` called unnecessarily | Pro injects insets natively | `Uniwind.updateInsets` is a no-op in Pro. Remove `SafeAreaListener` setup when using Pro |
-| Pro: theme transition crash | Missing `ThemeTransitionPreset` import or calling before app is ready | Import from `'uniwind'`. Ensure the app has fully mounted before calling `setTheme` with a transition |
-
-### unstable_enablePackageExports Selective Resolver
-
-If your app disables `unstable_enablePackageExports` (common in crypto apps), use a selective resolver:
-
-```js
-config.resolver.unstable_enablePackageExports = false;
-config.resolver.resolveRequest = (context, moduleName, platform) => {
-  if (['uniwind', 'culori'].some((prefix) => moduleName.startsWith(prefix))) {
-    return context.resolveRequest(
-      { ...context, unstable_enablePackageExports: true },
-      moduleName,
-      platform
-    );
-  }
-  return context.resolveRequest(context, moduleName, platform);
-};
-```
-
-## FAQ
-
-**Where to put global.css in Expo Router?**
-Project root. Import in `app/_layout.tsx`. If placed in `app/`, add `@source` for sibling dirs.
-
-**Does Uniwind work with Expo Go?**
-Free: Yes. Pro: No — requires native rebuild (development builds).
-
-**Can I use tailwind.config.js?**
-No. Uniwind uses Tailwind v4 — all config via `@theme` in `global.css`.
-
-**How to access CSS variables in JS?**
-Inside components: `useCSSVariable('--color-primary')` (reactive). Outside React: `Uniwind.getCSSVariable('--color-primary')` (one-shot, 1.6.4+). For variables not used in classNames, define with `@theme static`.
-
-**Can I use Platform.select()?**
-Yes, but prefer platform selectors (`ios:pt-12 android:pt-6`) — cleaner, no imports.
-
-**Next.js support?**
-Not officially supported. Community plugin: `uniwind-plugin-next`. For Next.js, use standard Tailwind CSS.
-
-**Vite support?**
-Yes, since v1.2.0. Use `uniwind/vite` plugin alongside `@tailwindcss/vite`.
-
-**Full app reloads on CSS changes?**
-Metro can't hot-reload files with many providers. Move `global.css` import deeper in the component tree.
-
-**Style specificity?**
-Inline `style` always overrides `className`. Use `className` for static styles, inline only for truly dynamic values. Use `cn()` from tailwind-merge for component libraries where classNames may conflict.
-
-**How do I include custom fonts?**
-Load font files (Expo: `expo-font` plugin in `app.json`; Bare RN: `react-native-asset`), then map in CSS: `@theme { --font-sans: 'Roboto-Regular'; }`. Font name must exactly match the file name. See the **Fonts** section above.
-
-**How can I style based on prop values?**
-Use data selectors: `data-[selected=true]:ring-2`. Only equality checks supported. See the **Data Selectors** section above.
-
-**How can I use gradients?**
-Built-in: `bg-gradient-to-r from-red-500 to-green-500`. Also supports angle-based (`bg-linear-90`) and arbitrary values (`bg-linear-[45deg,#f00_0%,#00f_100%]`). See the **Gradients** section above.
-
-**How does className deduplication work?**
-Uniwind does NOT auto-deduplicate conflicting classNames. Use `tailwind-merge` with a `cn()` utility. See the **cn Utility** section above.
-
-**How to debug 'Failed to serialize javascript object'?**
-Clear caches: `watchman watch-del-all; rm -rf node_modules/.cache; npx expo start --clear`. Enable `debug: true` in metro config to identify the problematic CSS pattern. See the **Troubleshooting** table above.
-
-**How do I enable safe area classNames?**
-Free: Install `react-native-safe-area-context`, wrap root with `SafeAreaListener`, call `Uniwind.updateInsets(insets)`. Pro: Automatic — no setup. Then use `pt-safe`, `pb-safe`, etc. See the **Safe Area Utilities** section above.
-
-**What UI kits work well with Uniwind?**
-**React Native Reusables** (shadcn philosophy, copy-paste components) and **HeroUI Native** (complete library, optimized for Uniwind). Any library works via `withUniwind` wrapper. See the **UI Kit Compatibility** section above.
-
-**Can I scope a theme to a single component?**
-Yes, use `ScopedTheme`: `<ScopedTheme theme="dark"><Card /></ScopedTheme>`. It forces a theme for the subtree without changing the global theme. See the **Theming** section.
-
-**Does `active:` work with `react-native-gesture-handler` Pressable?**
-No. `withUniwind` does NOT support interactive state selectors (`active:`, `focus:`, `disabled:`). Only core RN `Pressable`, `TextInput`, and `Switch` support them. For RNGH components, use `onPressIn`/`onPressOut` with state.
-
-**Can I customize the default `border` color?**
-Yes — use `@utility border` to override the class entirely:
-```css
-@utility border {
-  border-width: 1px;
-  border-style: solid;
-  border-color: var(--color-primary);
-}
-```
-This completely replaces the built-in `border` behavior, so re-declare any properties you still need. Alternatively, use `border border-gray-300` explicitly or define `--color-border` in `@theme` and use `border-border`.
-
-**Can I use platform-specific fonts in `@theme {}`?**
-No. `@theme {}` only accepts custom properties. Use `@layer theme { :root { @variant ios { --font-sans: '...'; } } }` instead. Note: use `@variant` (not `@media`) for platform selection in CSS.
-
-**Does Uniwind work with ESM metro config (`metro.config.mjs` or `metro.config.ts`)?**
-Yes since v1.3.0+. If you hit `Class extends value is not a constructor`, update Uniwind or use CommonJS `metro.config.js`.
-
-**Classes from `node_modules` work in dev but not production (bun)?**
-Bun symlinks packages. Tailwind's Oxide scanner can't follow them. Use resolved `@source` paths and hoist the package. See **UI Kit Compatibility** for details.
-
-**How to clear all caches when debugging?**
-`watchman watch-del-all 2>/dev/null; rm -rf node_modules/.cache && npx expo start --clear`. This clears Watchman, Babel/bundler caches, and Expo internal cache.
-
-## MCP Server
-
-Uniwind provides an official MCP server for AI-assisted development. If the user's environment supports MCP, recommend setting it up: [https://docs.uniwind.dev/mcp](https://docs.uniwind.dev/mcp)
-
-## Related Skills
-
-**NativeWind → Uniwind migration**: Use the `migrate-nativewind-to-uniwind` skill for migrating from NativeWind. It handles package removal, config migration, Tailwind v4 upgrade, `cssInterop` removal, theme conversion, and all breaking changes. It is tested and production-ready.
-
-**IMPORTANT**: Do NOT guess Uniwind APIs. If you are unsure about any Uniwind API, hook, component, or configuration option, fetch and verify against the official docs: [https://docs.uniwind.dev/llms-full.txt](https://docs.uniwind.dev/llms-full.txt)

+ 0 - 946
neo/.agents/skills/vercel-composition-patterns/AGENTS.md

@@ -1,946 +0,0 @@
-# React Composition Patterns
-
-**Version 1.0.0**  
-Engineering  
-January 2026
-
-> **Note:**  
-> This document is mainly for agents and LLMs to follow when maintaining,  
-> generating, or refactoring React codebases using composition. Humans  
-> may also find it useful, but guidance here is optimized for automation  
-> and consistency by AI-assisted workflows.
-
----
-
-## Abstract
-
-Composition patterns for building flexible, maintainable React components. Avoid boolean prop proliferation by using compound components, lifting state, and composing internals. These patterns make codebases easier for both humans and AI agents to work with as they scale.
-
----
-
-## Table of Contents
-
-1. [Component Architecture](#1-component-architecture) — **HIGH**
-   - 1.1 [Avoid Boolean Prop Proliferation](#11-avoid-boolean-prop-proliferation)
-   - 1.2 [Use Compound Components](#12-use-compound-components)
-2. [State Management](#2-state-management) — **MEDIUM**
-   - 2.1 [Decouple State Management from UI](#21-decouple-state-management-from-ui)
-   - 2.2 [Define Generic Context Interfaces for Dependency Injection](#22-define-generic-context-interfaces-for-dependency-injection)
-   - 2.3 [Lift State into Provider Components](#23-lift-state-into-provider-components)
-3. [Implementation Patterns](#3-implementation-patterns) — **MEDIUM**
-   - 3.1 [Create Explicit Component Variants](#31-create-explicit-component-variants)
-   - 3.2 [Prefer Composing Children Over Render Props](#32-prefer-composing-children-over-render-props)
-4. [React 19 APIs](#4-react-19-apis) — **MEDIUM**
-   - 4.1 [React 19 API Changes](#41-react-19-api-changes)
-
----
-
-## 1. Component Architecture
-
-**Impact: HIGH**
-
-Fundamental patterns for structuring components to avoid prop
-proliferation and enable flexible composition.
-
-### 1.1 Avoid Boolean Prop Proliferation
-
-**Impact: CRITICAL (prevents unmaintainable component variants)**
-
-Don't add boolean props like `isThread`, `isEditing`, `isDMThread` to customize
-
-component behavior. Each boolean doubles possible states and creates
-
-unmaintainable conditional logic. Use composition instead.
-
-**Incorrect: boolean props create exponential complexity**
-
-```tsx
-function Composer({
-  onSubmit,
-  isThread,
-  channelId,
-  isDMThread,
-  dmId,
-  isEditing,
-  isForwarding,
-}: Props) {
-  return (
-    <form>
-      <Header />
-      <Input />
-      {isDMThread ? (
-        <AlsoSendToDMField id={dmId} />
-      ) : isThread ? (
-        <AlsoSendToChannelField id={channelId} />
-      ) : null}
-      {isEditing ? (
-        <EditActions />
-      ) : isForwarding ? (
-        <ForwardActions />
-      ) : (
-        <DefaultActions />
-      )}
-      <Footer onSubmit={onSubmit} />
-    </form>
-  )
-}
-```
-
-**Correct: composition eliminates conditionals**
-
-```tsx
-// Channel composer
-function ChannelComposer() {
-  return (
-    <Composer.Frame>
-      <Composer.Header />
-      <Composer.Input />
-      <Composer.Footer>
-        <Composer.Attachments />
-        <Composer.Formatting />
-        <Composer.Emojis />
-        <Composer.Submit />
-      </Composer.Footer>
-    </Composer.Frame>
-  )
-}
-
-// Thread composer - adds "also send to channel" field
-function ThreadComposer({ channelId }: { channelId: string }) {
-  return (
-    <Composer.Frame>
-      <Composer.Header />
-      <Composer.Input />
-      <AlsoSendToChannelField id={channelId} />
-      <Composer.Footer>
-        <Composer.Formatting />
-        <Composer.Emojis />
-        <Composer.Submit />
-      </Composer.Footer>
-    </Composer.Frame>
-  )
-}
-
-// Edit composer - different footer actions
-function EditComposer() {
-  return (
-    <Composer.Frame>
-      <Composer.Input />
-      <Composer.Footer>
-        <Composer.Formatting />
-        <Composer.Emojis />
-        <Composer.CancelEdit />
-        <Composer.SaveEdit />
-      </Composer.Footer>
-    </Composer.Frame>
-  )
-}
-```
-
-Each variant is explicit about what it renders. We can share internals without
-
-sharing a single monolithic parent.
-
-### 1.2 Use Compound Components
-
-**Impact: HIGH (enables flexible composition without prop drilling)**
-
-Structure complex components as compound components with a shared context. Each
-
-subcomponent accesses shared state via context, not props. Consumers compose the
-
-pieces they need.
-
-**Incorrect: monolithic component with render props**
-
-```tsx
-function Composer({
-  renderHeader,
-  renderFooter,
-  renderActions,
-  showAttachments,
-  showFormatting,
-  showEmojis,
-}: Props) {
-  return (
-    <form>
-      {renderHeader?.()}
-      <Input />
-      {showAttachments && <Attachments />}
-      {renderFooter ? (
-        renderFooter()
-      ) : (
-        <Footer>
-          {showFormatting && <Formatting />}
-          {showEmojis && <Emojis />}
-          {renderActions?.()}
-        </Footer>
-      )}
-    </form>
-  )
-}
-```
-
-**Correct: compound components with shared context**
-
-```tsx
-const ComposerContext = createContext<ComposerContextValue | null>(null)
-
-function ComposerProvider({ children, state, actions, meta }: ProviderProps) {
-  return (
-    <ComposerContext value={{ state, actions, meta }}>
-      {children}
-    </ComposerContext>
-  )
-}
-
-function ComposerFrame({ children }: { children: React.ReactNode }) {
-  return <form>{children}</form>
-}
-
-function ComposerInput() {
-  const {
-    state,
-    actions: { update },
-    meta: { inputRef },
-  } = use(ComposerContext)
-  return (
-    <TextInput
-      ref={inputRef}
-      value={state.input}
-      onChangeText={(text) => update((s) => ({ ...s, input: text }))}
-    />
-  )
-}
-
-function ComposerSubmit() {
-  const {
-    actions: { submit },
-  } = use(ComposerContext)
-  return <Button onPress={submit}>Send</Button>
-}
-
-// Export as compound component
-const Composer = {
-  Provider: ComposerProvider,
-  Frame: ComposerFrame,
-  Input: ComposerInput,
-  Submit: ComposerSubmit,
-  Header: ComposerHeader,
-  Footer: ComposerFooter,
-  Attachments: ComposerAttachments,
-  Formatting: ComposerFormatting,
-  Emojis: ComposerEmojis,
-}
-```
-
-**Usage:**
-
-```tsx
-<Composer.Provider state={state} actions={actions} meta={meta}>
-  <Composer.Frame>
-    <Composer.Header />
-    <Composer.Input />
-    <Composer.Footer>
-      <Composer.Formatting />
-      <Composer.Submit />
-    </Composer.Footer>
-  </Composer.Frame>
-</Composer.Provider>
-```
-
-Consumers explicitly compose exactly what they need. No hidden conditionals. And the state, actions and meta are dependency-injected by a parent provider, allowing multiple usages of the same component structure.
-
----
-
-## 2. State Management
-
-**Impact: MEDIUM**
-
-Patterns for lifting state and managing shared context across
-composed components.
-
-### 2.1 Decouple State Management from UI
-
-**Impact: MEDIUM (enables swapping state implementations without changing UI)**
-
-The provider component should be the only place that knows how state is managed.
-
-UI components consume the context interface—they don't know if state comes from
-
-useState, Zustand, or a server sync.
-
-**Incorrect: UI coupled to state implementation**
-
-```tsx
-function ChannelComposer({ channelId }: { channelId: string }) {
-  // UI component knows about global state implementation
-  const state = useGlobalChannelState(channelId)
-  const { submit, updateInput } = useChannelSync(channelId)
-
-  return (
-    <Composer.Frame>
-      <Composer.Input
-        value={state.input}
-        onChange={(text) => sync.updateInput(text)}
-      />
-      <Composer.Submit onPress={() => sync.submit()} />
-    </Composer.Frame>
-  )
-}
-```
-
-**Correct: state management isolated in provider**
-
-```tsx
-// Provider handles all state management details
-function ChannelProvider({
-  channelId,
-  children,
-}: {
-  channelId: string
-  children: React.ReactNode
-}) {
-  const { state, update, submit } = useGlobalChannel(channelId)
-  const inputRef = useRef(null)
-
-  return (
-    <Composer.Provider
-      state={state}
-      actions={{ update, submit }}
-      meta={{ inputRef }}
-    >
-      {children}
-    </Composer.Provider>
-  )
-}
-
-// UI component only knows about the context interface
-function ChannelComposer() {
-  return (
-    <Composer.Frame>
-      <Composer.Header />
-      <Composer.Input />
-      <Composer.Footer>
-        <Composer.Submit />
-      </Composer.Footer>
-    </Composer.Frame>
-  )
-}
-
-// Usage
-function Channel({ channelId }: { channelId: string }) {
-  return (
-    <ChannelProvider channelId={channelId}>
-      <ChannelComposer />
-    </ChannelProvider>
-  )
-}
-```
-
-**Different providers, same UI:**
-
-```tsx
-// Local state for ephemeral forms
-function ForwardMessageProvider({ children }) {
-  const [state, setState] = useState(initialState)
-  const forwardMessage = useForwardMessage()
-
-  return (
-    <Composer.Provider
-      state={state}
-      actions={{ update: setState, submit: forwardMessage }}
-    >
-      {children}
-    </Composer.Provider>
-  )
-}
-
-// Global synced state for channels
-function ChannelProvider({ channelId, children }) {
-  const { state, update, submit } = useGlobalChannel(channelId)
-
-  return (
-    <Composer.Provider state={state} actions={{ update, submit }}>
-      {children}
-    </Composer.Provider>
-  )
-}
-```
-
-The same `Composer.Input` component works with both providers because it only
-
-depends on the context interface, not the implementation.
-
-### 2.2 Define Generic Context Interfaces for Dependency Injection
-
-**Impact: HIGH (enables dependency-injectable state across use-cases)**
-
-Define a **generic interface** for your component context with three parts:
-
-`state`, `actions`, and `meta`. This interface is a contract that any provider
-
-can implement—enabling the same UI components to work with completely different
-
-state implementations.
-
-**Core principle:** Lift state, compose internals, make state
-
-dependency-injectable.
-
-**Incorrect: UI coupled to specific state implementation**
-
-```tsx
-function ComposerInput() {
-  // Tightly coupled to a specific hook
-  const { input, setInput } = useChannelComposerState()
-  return <TextInput value={input} onChangeText={setInput} />
-}
-```
-
-**Correct: generic interface enables dependency injection**
-
-```tsx
-// Define a GENERIC interface that any provider can implement
-interface ComposerState {
-  input: string
-  attachments: Attachment[]
-  isSubmitting: boolean
-}
-
-interface ComposerActions {
-  update: (updater: (state: ComposerState) => ComposerState) => void
-  submit: () => void
-}
-
-interface ComposerMeta {
-  inputRef: React.RefObject<TextInput>
-}
-
-interface ComposerContextValue {
-  state: ComposerState
-  actions: ComposerActions
-  meta: ComposerMeta
-}
-
-const ComposerContext = createContext<ComposerContextValue | null>(null)
-```
-
-**UI components consume the interface, not the implementation:**
-
-```tsx
-function ComposerInput() {
-  const {
-    state,
-    actions: { update },
-    meta,
-  } = use(ComposerContext)
-
-  // This component works with ANY provider that implements the interface
-  return (
-    <TextInput
-      ref={meta.inputRef}
-      value={state.input}
-      onChangeText={(text) => update((s) => ({ ...s, input: text }))}
-    />
-  )
-}
-```
-
-**Different providers implement the same interface:**
-
-```tsx
-// Provider A: Local state for ephemeral forms
-function ForwardMessageProvider({ children }: { children: React.ReactNode }) {
-  const [state, setState] = useState(initialState)
-  const inputRef = useRef(null)
-  const submit = useForwardMessage()
-
-  return (
-    <ComposerContext
-      value={{
-        state,
-        actions: { update: setState, submit },
-        meta: { inputRef },
-      }}
-    >
-      {children}
-    </ComposerContext>
-  )
-}
-
-// Provider B: Global synced state for channels
-function ChannelProvider({ channelId, children }: Props) {
-  const { state, update, submit } = useGlobalChannel(channelId)
-  const inputRef = useRef(null)
-
-  return (
-    <ComposerContext
-      value={{
-        state,
-        actions: { update, submit },
-        meta: { inputRef },
-      }}
-    >
-      {children}
-    </ComposerContext>
-  )
-}
-```
-
-**The same composed UI works with both:**
-
-```tsx
-// Works with ForwardMessageProvider (local state)
-<ForwardMessageProvider>
-  <Composer.Frame>
-    <Composer.Input />
-    <Composer.Submit />
-  </Composer.Frame>
-</ForwardMessageProvider>
-
-// Works with ChannelProvider (global synced state)
-<ChannelProvider channelId="abc">
-  <Composer.Frame>
-    <Composer.Input />
-    <Composer.Submit />
-  </Composer.Frame>
-</ChannelProvider>
-```
-
-**Custom UI outside the component can access state and actions:**
-
-```tsx
-function ForwardMessageDialog() {
-  return (
-    <ForwardMessageProvider>
-      <Dialog>
-        {/* The composer UI */}
-        <Composer.Frame>
-          <Composer.Input placeholder="Add a message, if you'd like." />
-          <Composer.Footer>
-            <Composer.Formatting />
-            <Composer.Emojis />
-          </Composer.Footer>
-        </Composer.Frame>
-
-        {/* Custom UI OUTSIDE the composer, but INSIDE the provider */}
-        <MessagePreview />
-
-        {/* Actions at the bottom of the dialog */}
-        <DialogActions>
-          <CancelButton />
-          <ForwardButton />
-        </DialogActions>
-      </Dialog>
-    </ForwardMessageProvider>
-  )
-}
-
-// This button lives OUTSIDE Composer.Frame but can still submit based on its context!
-function ForwardButton() {
-  const {
-    actions: { submit },
-  } = use(ComposerContext)
-  return <Button onPress={submit}>Forward</Button>
-}
-
-// This preview lives OUTSIDE Composer.Frame but can read composer's state!
-function MessagePreview() {
-  const { state } = use(ComposerContext)
-  return <Preview message={state.input} attachments={state.attachments} />
-}
-```
-
-The provider boundary is what matters—not the visual nesting. Components that
-
-need shared state don't have to be inside the `Composer.Frame`. They just need
-
-to be within the provider.
-
-The `ForwardButton` and `MessagePreview` are not visually inside the composer
-
-box, but they can still access its state and actions. This is the power of
-
-lifting state into providers.
-
-The UI is reusable bits you compose together. The state is dependency-injected
-
-by the provider. Swap the provider, keep the UI.
-
-### 2.3 Lift State into Provider Components
-
-**Impact: HIGH (enables state sharing outside component boundaries)**
-
-Move state management into dedicated provider components. This allows sibling
-
-components outside the main UI to access and modify state without prop drilling
-
-or awkward refs.
-
-**Incorrect: state trapped inside component**
-
-```tsx
-function ForwardMessageComposer() {
-  const [state, setState] = useState(initialState)
-  const forwardMessage = useForwardMessage()
-
-  return (
-    <Composer.Frame>
-      <Composer.Input />
-      <Composer.Footer />
-    </Composer.Frame>
-  )
-}
-
-// Problem: How does this button access composer state?
-function ForwardMessageDialog() {
-  return (
-    <Dialog>
-      <ForwardMessageComposer />
-      <MessagePreview /> {/* Needs composer state */}
-      <DialogActions>
-        <CancelButton />
-        <ForwardButton /> {/* Needs to call submit */}
-      </DialogActions>
-    </Dialog>
-  )
-}
-```
-
-**Incorrect: useEffect to sync state up**
-
-```tsx
-function ForwardMessageDialog() {
-  const [input, setInput] = useState('')
-  return (
-    <Dialog>
-      <ForwardMessageComposer onInputChange={setInput} />
-      <MessagePreview input={input} />
-    </Dialog>
-  )
-}
-
-function ForwardMessageComposer({ onInputChange }) {
-  const [state, setState] = useState(initialState)
-  useEffect(() => {
-    onInputChange(state.input) // Sync on every change 😬
-  }, [state.input])
-}
-```
-
-**Incorrect: reading state from ref on submit**
-
-```tsx
-function ForwardMessageDialog() {
-  const stateRef = useRef(null)
-  return (
-    <Dialog>
-      <ForwardMessageComposer stateRef={stateRef} />
-      <ForwardButton onPress={() => submit(stateRef.current)} />
-    </Dialog>
-  )
-}
-```
-
-**Correct: state lifted to provider**
-
-```tsx
-function ForwardMessageProvider({ children }: { children: React.ReactNode }) {
-  const [state, setState] = useState(initialState)
-  const forwardMessage = useForwardMessage()
-  const inputRef = useRef(null)
-
-  return (
-    <Composer.Provider
-      state={state}
-      actions={{ update: setState, submit: forwardMessage }}
-      meta={{ inputRef }}
-    >
-      {children}
-    </Composer.Provider>
-  )
-}
-
-function ForwardMessageDialog() {
-  return (
-    <ForwardMessageProvider>
-      <Dialog>
-        <ForwardMessageComposer />
-        <MessagePreview /> {/* Custom components can access state and actions */}
-        <DialogActions>
-          <CancelButton />
-          <ForwardButton /> {/* Custom components can access state and actions */}
-        </DialogActions>
-      </Dialog>
-    </ForwardMessageProvider>
-  )
-}
-
-function ForwardButton() {
-  const { actions } = use(Composer.Context)
-  return <Button onPress={actions.submit}>Forward</Button>
-}
-```
-
-The ForwardButton lives outside the Composer.Frame but still has access to the
-
-submit action because it's within the provider. Even though it's a one-off
-
-component, it can still access the composer's state and actions from outside the
-
-UI itself.
-
-**Key insight:** Components that need shared state don't have to be visually
-
-nested inside each other—they just need to be within the same provider.
-
----
-
-## 3. Implementation Patterns
-
-**Impact: MEDIUM**
-
-Specific techniques for implementing compound components and
-context providers.
-
-### 3.1 Create Explicit Component Variants
-
-**Impact: MEDIUM (self-documenting code, no hidden conditionals)**
-
-Instead of one component with many boolean props, create explicit variant
-
-components. Each variant composes the pieces it needs. The code documents
-
-itself.
-
-**Incorrect: one component, many modes**
-
-```tsx
-// What does this component actually render?
-<Composer
-  isThread
-  isEditing={false}
-  channelId='abc'
-  showAttachments
-  showFormatting={false}
-/>
-```
-
-**Correct: explicit variants**
-
-```tsx
-// Immediately clear what this renders
-<ThreadComposer channelId="abc" />
-
-// Or
-<EditMessageComposer messageId="xyz" />
-
-// Or
-<ForwardMessageComposer messageId="123" />
-```
-
-Each implementation is unique, explicit and self-contained. Yet they can each
-
-use shared parts.
-
-**Implementation:**
-
-```tsx
-function ThreadComposer({ channelId }: { channelId: string }) {
-  return (
-    <ThreadProvider channelId={channelId}>
-      <Composer.Frame>
-        <Composer.Input />
-        <AlsoSendToChannelField channelId={channelId} />
-        <Composer.Footer>
-          <Composer.Formatting />
-          <Composer.Emojis />
-          <Composer.Submit />
-        </Composer.Footer>
-      </Composer.Frame>
-    </ThreadProvider>
-  )
-}
-
-function EditMessageComposer({ messageId }: { messageId: string }) {
-  return (
-    <EditMessageProvider messageId={messageId}>
-      <Composer.Frame>
-        <Composer.Input />
-        <Composer.Footer>
-          <Composer.Formatting />
-          <Composer.Emojis />
-          <Composer.CancelEdit />
-          <Composer.SaveEdit />
-        </Composer.Footer>
-      </Composer.Frame>
-    </EditMessageProvider>
-  )
-}
-
-function ForwardMessageComposer({ messageId }: { messageId: string }) {
-  return (
-    <ForwardMessageProvider messageId={messageId}>
-      <Composer.Frame>
-        <Composer.Input placeholder="Add a message, if you'd like." />
-        <Composer.Footer>
-          <Composer.Formatting />
-          <Composer.Emojis />
-          <Composer.Mentions />
-        </Composer.Footer>
-      </Composer.Frame>
-    </ForwardMessageProvider>
-  )
-}
-```
-
-Each variant is explicit about:
-
-- What provider/state it uses
-
-- What UI elements it includes
-
-- What actions are available
-
-No boolean prop combinations to reason about. No impossible states.
-
-### 3.2 Prefer Composing Children Over Render Props
-
-**Impact: MEDIUM (cleaner composition, better readability)**
-
-Use `children` for composition instead of `renderX` props. Children are more
-
-readable, compose naturally, and don't require understanding callback
-
-signatures.
-
-**Incorrect: render props**
-
-```tsx
-function Composer({
-  renderHeader,
-  renderFooter,
-  renderActions,
-}: {
-  renderHeader?: () => React.ReactNode
-  renderFooter?: () => React.ReactNode
-  renderActions?: () => React.ReactNode
-}) {
-  return (
-    <form>
-      {renderHeader?.()}
-      <Input />
-      {renderFooter ? renderFooter() : <DefaultFooter />}
-      {renderActions?.()}
-    </form>
-  )
-}
-
-// Usage is awkward and inflexible
-return (
-  <Composer
-    renderHeader={() => <CustomHeader />}
-    renderFooter={() => (
-      <>
-        <Formatting />
-        <Emojis />
-      </>
-    )}
-    renderActions={() => <SubmitButton />}
-  />
-)
-```
-
-**Correct: compound components with children**
-
-```tsx
-function ComposerFrame({ children }: { children: React.ReactNode }) {
-  return <form>{children}</form>
-}
-
-function ComposerFooter({ children }: { children: React.ReactNode }) {
-  return <footer className='flex'>{children}</footer>
-}
-
-// Usage is flexible
-return (
-  <Composer.Frame>
-    <CustomHeader />
-    <Composer.Input />
-    <Composer.Footer>
-      <Composer.Formatting />
-      <Composer.Emojis />
-      <SubmitButton />
-    </Composer.Footer>
-  </Composer.Frame>
-)
-```
-
-**When render props are appropriate:**
-
-```tsx
-// Render props work well when you need to pass data back
-<List
-  data={items}
-  renderItem={({ item, index }) => <Item item={item} index={index} />}
-/>
-```
-
-Use render props when the parent needs to provide data or state to the child.
-
-Use children when composing static structure.
-
----
-
-## 4. React 19 APIs
-
-**Impact: MEDIUM**
-
-React 19+ only. Don't use `forwardRef`; use `use()` instead of `useContext()`.
-
-### 4.1 React 19 API Changes
-
-**Impact: MEDIUM (cleaner component definitions and context usage)**
-
-> **⚠️ React 19+ only.** Skip this if you're on React 18 or earlier.
-
-In React 19, `ref` is now a regular prop (no `forwardRef` wrapper needed), and `use()` replaces `useContext()`.
-
-**Incorrect: forwardRef in React 19**
-
-```tsx
-const ComposerInput = forwardRef<TextInput, Props>((props, ref) => {
-  return <TextInput ref={ref} {...props} />
-})
-```
-
-**Correct: ref as a regular prop**
-
-```tsx
-function ComposerInput({ ref, ...props }: Props & { ref?: React.Ref<TextInput> }) {
-  return <TextInput ref={ref} {...props} />
-}
-```
-
-**Incorrect: useContext in React 19**
-
-```tsx
-const value = useContext(MyContext)
-```
-
-**Correct: use instead of useContext**
-
-```tsx
-const value = use(MyContext)
-```
-
-`use()` can also be called conditionally, unlike `useContext()`.
-
----
-
-## References
-
-1. [https://react.dev](https://react.dev)
-2. [https://react.dev/learn/passing-data-deeply-with-context](https://react.dev/learn/passing-data-deeply-with-context)
-3. [https://react.dev/reference/react/use](https://react.dev/reference/react/use)

+ 0 - 60
neo/.agents/skills/vercel-composition-patterns/README.md

@@ -1,60 +0,0 @@
-# React Composition Patterns
-
-A structured repository for React composition patterns that scale. These
-patterns help avoid boolean prop proliferation by using compound components,
-lifting state, and composing internals.
-
-## Structure
-
-- `rules/` - Individual rule files (one per rule)
-  - `_sections.md` - Section metadata (titles, impacts, descriptions)
-  - `_template.md` - Template for creating new rules
-  - `area-description.md` - Individual rule files
-- `metadata.json` - Document metadata (version, organization, abstract)
-- **`AGENTS.md`** - Compiled output (generated)
-
-## Rules
-
-### Component Architecture (CRITICAL)
-
-- `architecture-avoid-boolean-props.md` - Don't add boolean props to customize
-  behavior
-- `architecture-compound-components.md` - Structure as compound components with
-  shared context
-
-### State Management (HIGH)
-
-- `state-lift-state.md` - Lift state into provider components
-- `state-context-interface.md` - Define clear context interfaces
-  (state/actions/meta)
-- `state-decouple-implementation.md` - Decouple state management from UI
-
-### Implementation Patterns (MEDIUM)
-
-- `patterns-children-over-render-props.md` - Prefer children over renderX props
-- `patterns-explicit-variants.md` - Create explicit component variants
-
-## Core Principles
-
-1. **Composition over configuration** — Instead of adding props, let consumers
-   compose
-2. **Lift your state** — State in providers, not trapped in components
-3. **Compose your internals** — Subcomponents access context, not props
-4. **Explicit variants** — Create ThreadComposer, EditComposer, not Composer
-   with isThread
-
-## Creating a New Rule
-
-1. Copy `rules/_template.md` to `rules/area-description.md`
-2. Choose the appropriate area prefix:
-   - `architecture-` for Component Architecture
-   - `state-` for State Management
-   - `patterns-` for Implementation Patterns
-3. Fill in the frontmatter and content
-4. Ensure you have clear examples with explanations
-
-## Impact Levels
-
-- `CRITICAL` - Foundational patterns, prevents unmaintainable code
-- `HIGH` - Significant maintainability improvements
-- `MEDIUM` - Good practices for cleaner code

+ 0 - 89
neo/.agents/skills/vercel-composition-patterns/SKILL.md

@@ -1,89 +0,0 @@
----
-name: vercel-composition-patterns
-description:
-  React composition patterns that scale. Use when refactoring components with
-  boolean prop proliferation, building flexible component libraries, or
-  designing reusable APIs. Triggers on tasks involving compound components,
-  render props, context providers, or component architecture. Includes React 19
-  API changes.
-license: MIT
-metadata:
-  author: vercel
-  version: '1.0.0'
----
-
-# React Composition Patterns
-
-Composition patterns for building flexible, maintainable React components. Avoid
-boolean prop proliferation by using compound components, lifting state, and
-composing internals. These patterns make codebases easier for both humans and AI
-agents to work with as they scale.
-
-## When to Apply
-
-Reference these guidelines when:
-
-- Refactoring components with many boolean props
-- Building reusable component libraries
-- Designing flexible component APIs
-- Reviewing component architecture
-- Working with compound components or context providers
-
-## Rule Categories by Priority
-
-| Priority | Category                | Impact | Prefix          |
-| -------- | ----------------------- | ------ | --------------- |
-| 1        | Component Architecture  | HIGH   | `architecture-` |
-| 2        | State Management        | MEDIUM | `state-`        |
-| 3        | Implementation Patterns | MEDIUM | `patterns-`     |
-| 4        | React 19 APIs           | MEDIUM | `react19-`      |
-
-## Quick Reference
-
-### 1. Component Architecture (HIGH)
-
-- `architecture-avoid-boolean-props` - Don't add boolean props to customize
-  behavior; use composition
-- `architecture-compound-components` - Structure complex components with shared
-  context
-
-### 2. State Management (MEDIUM)
-
-- `state-decouple-implementation` - Provider is the only place that knows how
-  state is managed
-- `state-context-interface` - Define generic interface with state, actions, meta
-  for dependency injection
-- `state-lift-state` - Move state into provider components for sibling access
-
-### 3. Implementation Patterns (MEDIUM)
-
-- `patterns-explicit-variants` - Create explicit variant components instead of
-  boolean modes
-- `patterns-children-over-render-props` - Use children for composition instead
-  of renderX props
-
-### 4. React 19 APIs (MEDIUM)
-
-> **⚠️ React 19+ only.** Skip this section if using React 18 or earlier.
-
-- `react19-no-forwardref` - Don't use `forwardRef`; use `use()` instead of `useContext()`
-
-## How to Use
-
-Read individual rule files for detailed explanations and code examples:
-
-```
-rules/architecture-avoid-boolean-props.md
-rules/state-context-interface.md
-```
-
-Each rule file contains:
-
-- Brief explanation of why it matters
-- Incorrect code example with explanation
-- Correct code example with explanation
-- Additional context and references
-
-## Full Compiled Document
-
-For the complete guide with all rules expanded: `AGENTS.md`

+ 0 - 29
neo/.agents/skills/vercel-composition-patterns/rules/_sections.md

@@ -1,29 +0,0 @@
-# Sections
-
-This file defines all sections, their ordering, impact levels, and descriptions.
-The section ID (in parentheses) is the filename prefix used to group rules.
-
----
-
-## 1. Component Architecture (architecture)
-
-**Impact:** HIGH  
-**Description:** Fundamental patterns for structuring components to avoid prop
-proliferation and enable flexible composition.
-
-## 2. State Management (state)
-
-**Impact:** MEDIUM  
-**Description:** Patterns for lifting state and managing shared context across
-composed components.
-
-## 3. Implementation Patterns (patterns)
-
-**Impact:** MEDIUM  
-**Description:** Specific techniques for implementing compound components and
-context providers.
-
-## 4. React 19 APIs (react19)
-
-**Impact:** MEDIUM  
-**Description:** React 19+ only. Don't use `forwardRef`; use `use()` instead of `useContext()`.

+ 0 - 24
neo/.agents/skills/vercel-composition-patterns/rules/_template.md

@@ -1,24 +0,0 @@
----
-title: Rule Title Here
-impact: MEDIUM
-impactDescription: brief description of impact
-tags: composition, components
----
-
-## Rule Title Here
-
-Brief explanation of the rule and why it matters.
-
-**Incorrect:**
-
-```tsx
-// Bad code example
-```
-
-**Correct:**
-
-```tsx
-// Good code example
-```
-
-Reference: [Link](https://example.com)

+ 0 - 100
neo/.agents/skills/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md

@@ -1,100 +0,0 @@
----
-title: Avoid Boolean Prop Proliferation
-impact: CRITICAL
-impactDescription: prevents unmaintainable component variants
-tags: composition, props, architecture
----
-
-## Avoid Boolean Prop Proliferation
-
-Don't add boolean props like `isThread`, `isEditing`, `isDMThread` to customize
-component behavior. Each boolean doubles possible states and creates
-unmaintainable conditional logic. Use composition instead.
-
-**Incorrect (boolean props create exponential complexity):**
-
-```tsx
-function Composer({
-  onSubmit,
-  isThread,
-  channelId,
-  isDMThread,
-  dmId,
-  isEditing,
-  isForwarding,
-}: Props) {
-  return (
-    <form>
-      <Header />
-      <Input />
-      {isDMThread ? (
-        <AlsoSendToDMField id={dmId} />
-      ) : isThread ? (
-        <AlsoSendToChannelField id={channelId} />
-      ) : null}
-      {isEditing ? (
-        <EditActions />
-      ) : isForwarding ? (
-        <ForwardActions />
-      ) : (
-        <DefaultActions />
-      )}
-      <Footer onSubmit={onSubmit} />
-    </form>
-  )
-}
-```
-
-**Correct (composition eliminates conditionals):**
-
-```tsx
-// Channel composer
-function ChannelComposer() {
-  return (
-    <Composer.Frame>
-      <Composer.Header />
-      <Composer.Input />
-      <Composer.Footer>
-        <Composer.Attachments />
-        <Composer.Formatting />
-        <Composer.Emojis />
-        <Composer.Submit />
-      </Composer.Footer>
-    </Composer.Frame>
-  )
-}
-
-// Thread composer - adds "also send to channel" field
-function ThreadComposer({ channelId }: { channelId: string }) {
-  return (
-    <Composer.Frame>
-      <Composer.Header />
-      <Composer.Input />
-      <AlsoSendToChannelField id={channelId} />
-      <Composer.Footer>
-        <Composer.Formatting />
-        <Composer.Emojis />
-        <Composer.Submit />
-      </Composer.Footer>
-    </Composer.Frame>
-  )
-}
-
-// Edit composer - different footer actions
-function EditComposer() {
-  return (
-    <Composer.Frame>
-      <Composer.Input />
-      <Composer.Footer>
-        <Composer.Formatting />
-        <Composer.Emojis />
-        <Composer.CancelEdit />
-        <Composer.SaveEdit />
-      </Composer.Footer>
-    </Composer.Frame>
-  )
-}
-```
-
-Each variant is explicit about what it renders. We can share internals without
-sharing a single monolithic parent.

+ 0 - 112
neo/.agents/skills/vercel-composition-patterns/rules/architecture-compound-components.md

@@ -1,112 +0,0 @@
----
-title: Use Compound Components
-impact: HIGH
-impactDescription: enables flexible composition without prop drilling
-tags: composition, compound-components, architecture
----
-
-## Use Compound Components
-
-Structure complex components as compound components with a shared context. Each
-subcomponent accesses shared state via context, not props. Consumers compose the
-pieces they need.
-
-**Incorrect (monolithic component with render props):**
-
-```tsx
-function Composer({
-  renderHeader,
-  renderFooter,
-  renderActions,
-  showAttachments,
-  showFormatting,
-  showEmojis,
-}: Props) {
-  return (
-    <form>
-      {renderHeader?.()}
-      <Input />
-      {showAttachments && <Attachments />}
-      {renderFooter ? (
-        renderFooter()
-      ) : (
-        <Footer>
-          {showFormatting && <Formatting />}
-          {showEmojis && <Emojis />}
-          {renderActions?.()}
-        </Footer>
-      )}
-    </form>
-  )
-}
-```
-
-**Correct (compound components with shared context):**
-
-```tsx
-const ComposerContext = createContext<ComposerContextValue | null>(null)
-
-function ComposerProvider({ children, state, actions, meta }: ProviderProps) {
-  return (
-    <ComposerContext value={{ state, actions, meta }}>
-      {children}
-    </ComposerContext>
-  )
-}
-
-function ComposerFrame({ children }: { children: React.ReactNode }) {
-  return <form>{children}</form>
-}
-
-function ComposerInput() {
-  const {
-    state,
-    actions: { update },
-    meta: { inputRef },
-  } = use(ComposerContext)
-  return (
-    <TextInput
-      ref={inputRef}
-      value={state.input}
-      onChangeText={(text) => update((s) => ({ ...s, input: text }))}
-    />
-  )
-}
-
-function ComposerSubmit() {
-  const {
-    actions: { submit },
-  } = use(ComposerContext)
-  return <Button onPress={submit}>Send</Button>
-}
-
-// Export as compound component
-const Composer = {
-  Provider: ComposerProvider,
-  Frame: ComposerFrame,
-  Input: ComposerInput,
-  Submit: ComposerSubmit,
-  Header: ComposerHeader,
-  Footer: ComposerFooter,
-  Attachments: ComposerAttachments,
-  Formatting: ComposerFormatting,
-  Emojis: ComposerEmojis,
-}
-```
-
-**Usage:**
-
-```tsx
-<Composer.Provider state={state} actions={actions} meta={meta}>
-  <Composer.Frame>
-    <Composer.Header />
-    <Composer.Input />
-    <Composer.Footer>
-      <Composer.Formatting />
-      <Composer.Submit />
-    </Composer.Footer>
-  </Composer.Frame>
-</Composer.Provider>
-```
-
-Consumers explicitly compose exactly what they need. No hidden conditionals. And the state, actions and meta are dependency-injected by a parent provider, allowing multiple usages of the same component structure.

+ 0 - 87
neo/.agents/skills/vercel-composition-patterns/rules/patterns-children-over-render-props.md

@@ -1,87 +0,0 @@
----
-title: Prefer Composing Children Over Render Props
-impact: MEDIUM
-impactDescription: cleaner composition, better readability
-tags: composition, children, render-props
----
-
-## Prefer Children Over Render Props
-
-Use `children` for composition instead of `renderX` props. Children are more
-readable, compose naturally, and don't require understanding callback
-signatures.
-
-**Incorrect (render props):**
-
-```tsx
-function Composer({
-  renderHeader,
-  renderFooter,
-  renderActions,
-}: {
-  renderHeader?: () => React.ReactNode
-  renderFooter?: () => React.ReactNode
-  renderActions?: () => React.ReactNode
-}) {
-  return (
-    <form>
-      {renderHeader?.()}
-      <Input />
-      {renderFooter ? renderFooter() : <DefaultFooter />}
-      {renderActions?.()}
-    </form>
-  )
-}
-
-// Usage is awkward and inflexible
-return (
-  <Composer
-    renderHeader={() => <CustomHeader />}
-    renderFooter={() => (
-      <>
-        <Formatting />
-        <Emojis />
-      </>
-    )}
-    renderActions={() => <SubmitButton />}
-  />
-)
-```
-
-**Correct (compound components with children):**
-
-```tsx
-function ComposerFrame({ children }: { children: React.ReactNode }) {
-  return <form>{children}</form>
-}
-
-function ComposerFooter({ children }: { children: React.ReactNode }) {
-  return <footer className='flex'>{children}</footer>
-}
-
-// Usage is flexible
-return (
-  <Composer.Frame>
-    <CustomHeader />
-    <Composer.Input />
-    <Composer.Footer>
-      <Composer.Formatting />
-      <Composer.Emojis />
-      <SubmitButton />
-    </Composer.Footer>
-  </Composer.Frame>
-)
-```
-
-**When render props are appropriate:**
-
-```tsx
-// Render props work well when you need to pass data back
-<List
-  data={items}
-  renderItem={({ item, index }) => <Item item={item} index={index} />}
-/>
-```
-
-Use render props when the parent needs to provide data or state to the child.
-Use children when composing static structure.

+ 0 - 100
neo/.agents/skills/vercel-composition-patterns/rules/patterns-explicit-variants.md

@@ -1,100 +0,0 @@
----
-title: Create Explicit Component Variants
-impact: MEDIUM
-impactDescription: self-documenting code, no hidden conditionals
-tags: composition, variants, architecture
----
-
-## Create Explicit Component Variants
-
-Instead of one component with many boolean props, create explicit variant
-components. Each variant composes the pieces it needs. The code documents
-itself.
-
-**Incorrect (one component, many modes):**
-
-```tsx
-// What does this component actually render?
-<Composer
-  isThread
-  isEditing={false}
-  channelId='abc'
-  showAttachments
-  showFormatting={false}
-/>
-```
-
-**Correct (explicit variants):**
-
-```tsx
-// Immediately clear what this renders
-<ThreadComposer channelId="abc" />
-
-// Or
-<EditMessageComposer messageId="xyz" />
-
-// Or
-<ForwardMessageComposer messageId="123" />
-```
-
-Each implementation is unique, explicit and self-contained. Yet they can each
-use shared parts.
-
-**Implementation:**
-
-```tsx
-function ThreadComposer({ channelId }: { channelId: string }) {
-  return (
-    <ThreadProvider channelId={channelId}>
-      <Composer.Frame>
-        <Composer.Input />
-        <AlsoSendToChannelField channelId={channelId} />
-        <Composer.Footer>
-          <Composer.Formatting />
-          <Composer.Emojis />
-          <Composer.Submit />
-        </Composer.Footer>
-      </Composer.Frame>
-    </ThreadProvider>
-  )
-}
-
-function EditMessageComposer({ messageId }: { messageId: string }) {
-  return (
-    <EditMessageProvider messageId={messageId}>
-      <Composer.Frame>
-        <Composer.Input />
-        <Composer.Footer>
-          <Composer.Formatting />
-          <Composer.Emojis />
-          <Composer.CancelEdit />
-          <Composer.SaveEdit />
-        </Composer.Footer>
-      </Composer.Frame>
-    </EditMessageProvider>
-  )
-}
-
-function ForwardMessageComposer({ messageId }: { messageId: string }) {
-  return (
-    <ForwardMessageProvider messageId={messageId}>
-      <Composer.Frame>
-        <Composer.Input placeholder="Add a message, if you'd like." />
-        <Composer.Footer>
-          <Composer.Formatting />
-          <Composer.Emojis />
-          <Composer.Mentions />
-        </Composer.Footer>
-      </Composer.Frame>
-    </ForwardMessageProvider>
-  )
-}
-```
-
-Each variant is explicit about:
-
-- What provider/state it uses
-- What UI elements it includes
-- What actions are available
-
-No boolean prop combinations to reason about. No impossible states.

+ 0 - 42
neo/.agents/skills/vercel-composition-patterns/rules/react19-no-forwardref.md

@@ -1,42 +0,0 @@
----
-title: React 19 API Changes
-impact: MEDIUM
-impactDescription: cleaner component definitions and context usage
-tags: react19, refs, context, hooks
----
-
-## React 19 API Changes
-
-> **⚠️ React 19+ only.** Skip this if you're on React 18 or earlier.
-
-In React 19, `ref` is now a regular prop (no `forwardRef` wrapper needed), and `use()` replaces `useContext()`.
-
-**Incorrect (forwardRef in React 19):**
-
-```tsx
-const ComposerInput = forwardRef<TextInput, Props>((props, ref) => {
-  return <TextInput ref={ref} {...props} />
-})
-```
-
-**Correct (ref as a regular prop):**
-
-```tsx
-function ComposerInput({ ref, ...props }: Props & { ref?: React.Ref<TextInput> }) {
-  return <TextInput ref={ref} {...props} />
-}
-```
-
-**Incorrect (useContext in React 19):**
-
-```tsx
-const value = useContext(MyContext)
-```
-
-**Correct (use instead of useContext):**
-
-```tsx
-const value = use(MyContext)
-```
-
-`use()` can also be called conditionally, unlike `useContext()`.

+ 0 - 191
neo/.agents/skills/vercel-composition-patterns/rules/state-context-interface.md

@@ -1,191 +0,0 @@
----
-title: Define Generic Context Interfaces for Dependency Injection
-impact: HIGH
-impactDescription: enables dependency-injectable state across use-cases
-tags: composition, context, state, typescript, dependency-injection
----
-
-## Define Generic Context Interfaces for Dependency Injection
-
-Define a **generic interface** for your component context with three parts:
-`state`, `actions`, and `meta`. This interface is a contract that any provider
-can implement—enabling the same UI components to work with completely different
-state implementations.
-
-**Core principle:** Lift state, compose internals, make state
-dependency-injectable.
-
-**Incorrect (UI coupled to specific state implementation):**
-
-```tsx
-function ComposerInput() {
-  // Tightly coupled to a specific hook
-  const { input, setInput } = useChannelComposerState()
-  return <TextInput value={input} onChangeText={setInput} />
-}
-```
-
-**Correct (generic interface enables dependency injection):**
-
-```tsx
-// Define a GENERIC interface that any provider can implement
-interface ComposerState {
-  input: string
-  attachments: Attachment[]
-  isSubmitting: boolean
-}
-
-interface ComposerActions {
-  update: (updater: (state: ComposerState) => ComposerState) => void
-  submit: () => void
-}
-
-interface ComposerMeta {
-  inputRef: React.RefObject<TextInput>
-}
-
-interface ComposerContextValue {
-  state: ComposerState
-  actions: ComposerActions
-  meta: ComposerMeta
-}
-
-const ComposerContext = createContext<ComposerContextValue | null>(null)
-```
-
-**UI components consume the interface, not the implementation:**
-
-```tsx
-function ComposerInput() {
-  const {
-    state,
-    actions: { update },
-    meta,
-  } = use(ComposerContext)
-
-  // This component works with ANY provider that implements the interface
-  return (
-    <TextInput
-      ref={meta.inputRef}
-      value={state.input}
-      onChangeText={(text) => update((s) => ({ ...s, input: text }))}
-    />
-  )
-}
-```
-
-**Different providers implement the same interface:**
-
-```tsx
-// Provider A: Local state for ephemeral forms
-function ForwardMessageProvider({ children }: { children: React.ReactNode }) {
-  const [state, setState] = useState(initialState)
-  const inputRef = useRef(null)
-  const submit = useForwardMessage()
-
-  return (
-    <ComposerContext
-      value={{
-        state,
-        actions: { update: setState, submit },
-        meta: { inputRef },
-      }}
-    >
-      {children}
-    </ComposerContext>
-  )
-}
-
-// Provider B: Global synced state for channels
-function ChannelProvider({ channelId, children }: Props) {
-  const { state, update, submit } = useGlobalChannel(channelId)
-  const inputRef = useRef(null)
-
-  return (
-    <ComposerContext
-      value={{
-        state,
-        actions: { update, submit },
-        meta: { inputRef },
-      }}
-    >
-      {children}
-    </ComposerContext>
-  )
-}
-```
-
-**The same composed UI works with both:**
-
-```tsx
-// Works with ForwardMessageProvider (local state)
-<ForwardMessageProvider>
-  <Composer.Frame>
-    <Composer.Input />
-    <Composer.Submit />
-  </Composer.Frame>
-</ForwardMessageProvider>
-
-// Works with ChannelProvider (global synced state)
-<ChannelProvider channelId="abc">
-  <Composer.Frame>
-    <Composer.Input />
-    <Composer.Submit />
-  </Composer.Frame>
-</ChannelProvider>
-```
-
-**Custom UI outside the component can access state and actions:**
-
-The provider boundary is what matters—not the visual nesting. Components that
-need shared state don't have to be inside the `Composer.Frame`. They just need
-to be within the provider.
-
-```tsx
-function ForwardMessageDialog() {
-  return (
-    <ForwardMessageProvider>
-      <Dialog>
-        {/* The composer UI */}
-        <Composer.Frame>
-          <Composer.Input placeholder="Add a message, if you'd like." />
-          <Composer.Footer>
-            <Composer.Formatting />
-            <Composer.Emojis />
-          </Composer.Footer>
-        </Composer.Frame>
-
-        {/* Custom UI OUTSIDE the composer, but INSIDE the provider */}
-        <MessagePreview />
-
-        {/* Actions at the bottom of the dialog */}
-        <DialogActions>
-          <CancelButton />
-          <ForwardButton />
-        </DialogActions>
-      </Dialog>
-    </ForwardMessageProvider>
-  )
-}
-
-// This button lives OUTSIDE Composer.Frame but can still submit based on its context!
-function ForwardButton() {
-  const {
-    actions: { submit },
-  } = use(ComposerContext)
-  return <Button onPress={submit}>Forward</Button>
-}
-
-// This preview lives OUTSIDE Composer.Frame but can read composer's state!
-function MessagePreview() {
-  const { state } = use(ComposerContext)
-  return <Preview message={state.input} attachments={state.attachments} />
-}
-```
-
-The `ForwardButton` and `MessagePreview` are not visually inside the composer
-box, but they can still access its state and actions. This is the power of
-lifting state into providers.
-
-The UI is reusable bits you compose together. The state is dependency-injected
-by the provider. Swap the provider, keep the UI.

+ 0 - 113
neo/.agents/skills/vercel-composition-patterns/rules/state-decouple-implementation.md

@@ -1,113 +0,0 @@
----
-title: Decouple State Management from UI
-impact: MEDIUM
-impactDescription: enables swapping state implementations without changing UI
-tags: composition, state, architecture
----
-
-## Decouple State Management from UI
-
-The provider component should be the only place that knows how state is managed.
-UI components consume the context interface—they don't know if state comes from
-useState, Zustand, or a server sync.
-
-**Incorrect (UI coupled to state implementation):**
-
-```tsx
-function ChannelComposer({ channelId }: { channelId: string }) {
-  // UI component knows about global state implementation
-  const state = useGlobalChannelState(channelId)
-  const { submit, updateInput } = useChannelSync(channelId)
-
-  return (
-    <Composer.Frame>
-      <Composer.Input
-        value={state.input}
-        onChange={(text) => sync.updateInput(text)}
-      />
-      <Composer.Submit onPress={() => sync.submit()} />
-    </Composer.Frame>
-  )
-}
-```
-
-**Correct (state management isolated in provider):**
-
-```tsx
-// Provider handles all state management details
-function ChannelProvider({
-  channelId,
-  children,
-}: {
-  channelId: string
-  children: React.ReactNode
-}) {
-  const { state, update, submit } = useGlobalChannel(channelId)
-  const inputRef = useRef(null)
-
-  return (
-    <Composer.Provider
-      state={state}
-      actions={{ update, submit }}
-      meta={{ inputRef }}
-    >
-      {children}
-    </Composer.Provider>
-  )
-}
-
-// UI component only knows about the context interface
-function ChannelComposer() {
-  return (
-    <Composer.Frame>
-      <Composer.Header />
-      <Composer.Input />
-      <Composer.Footer>
-        <Composer.Submit />
-      </Composer.Footer>
-    </Composer.Frame>
-  )
-}
-
-// Usage
-function Channel({ channelId }: { channelId: string }) {
-  return (
-    <ChannelProvider channelId={channelId}>
-      <ChannelComposer />
-    </ChannelProvider>
-  )
-}
-```
-
-**Different providers, same UI:**
-
-```tsx
-// Local state for ephemeral forms
-function ForwardMessageProvider({ children }) {
-  const [state, setState] = useState(initialState)
-  const forwardMessage = useForwardMessage()
-
-  return (
-    <Composer.Provider
-      state={state}
-      actions={{ update: setState, submit: forwardMessage }}
-    >
-      {children}
-    </Composer.Provider>
-  )
-}
-
-// Global synced state for channels
-function ChannelProvider({ channelId, children }) {
-  const { state, update, submit } = useGlobalChannel(channelId)
-
-  return (
-    <Composer.Provider state={state} actions={{ update, submit }}>
-      {children}
-    </Composer.Provider>
-  )
-}
-```
-
-The same `Composer.Input` component works with both providers because it only
-depends on the context interface, not the implementation.

+ 0 - 125
neo/.agents/skills/vercel-composition-patterns/rules/state-lift-state.md

@@ -1,125 +0,0 @@
----
-title: Lift State into Provider Components
-impact: HIGH
-impactDescription: enables state sharing outside component boundaries
-tags: composition, state, context, providers
----
-
-## Lift State into Provider Components
-
-Move state management into dedicated provider components. This allows sibling
-components outside the main UI to access and modify state without prop drilling
-or awkward refs.
-
-**Incorrect (state trapped inside component):**
-
-```tsx
-function ForwardMessageComposer() {
-  const [state, setState] = useState(initialState)
-  const forwardMessage = useForwardMessage()
-
-  return (
-    <Composer.Frame>
-      <Composer.Input />
-      <Composer.Footer />
-    </Composer.Frame>
-  )
-}
-
-// Problem: How does this button access composer state?
-function ForwardMessageDialog() {
-  return (
-    <Dialog>
-      <ForwardMessageComposer />
-      <MessagePreview /> {/* Needs composer state */}
-      <DialogActions>
-        <CancelButton />
-        <ForwardButton /> {/* Needs to call submit */}
-      </DialogActions>
-    </Dialog>
-  )
-}
-```
-
-**Incorrect (useEffect to sync state up):**
-
-```tsx
-function ForwardMessageDialog() {
-  const [input, setInput] = useState('')
-  return (
-    <Dialog>
-      <ForwardMessageComposer onInputChange={setInput} />
-      <MessagePreview input={input} />
-    </Dialog>
-  )
-}
-
-function ForwardMessageComposer({ onInputChange }) {
-  const [state, setState] = useState(initialState)
-  useEffect(() => {
-    onInputChange(state.input) // Sync on every change 😬
-  }, [state.input])
-}
-```
-
-**Incorrect (reading state from ref on submit):**
-
-```tsx
-function ForwardMessageDialog() {
-  const stateRef = useRef(null)
-  return (
-    <Dialog>
-      <ForwardMessageComposer stateRef={stateRef} />
-      <ForwardButton onPress={() => submit(stateRef.current)} />
-    </Dialog>
-  )
-}
-```
-
-**Correct (state lifted to provider):**
-
-```tsx
-function ForwardMessageProvider({ children }: { children: React.ReactNode }) {
-  const [state, setState] = useState(initialState)
-  const forwardMessage = useForwardMessage()
-  const inputRef = useRef(null)
-
-  return (
-    <Composer.Provider
-      state={state}
-      actions={{ update: setState, submit: forwardMessage }}
-      meta={{ inputRef }}
-    >
-      {children}
-    </Composer.Provider>
-  )
-}
-
-function ForwardMessageDialog() {
-  return (
-    <ForwardMessageProvider>
-      <Dialog>
-        <ForwardMessageComposer />
-        <MessagePreview /> {/* Custom components can access state and actions */}
-        <DialogActions>
-          <CancelButton />
-          <ForwardButton /> {/* Custom components can access state and actions */}
-        </DialogActions>
-      </Dialog>
-    </ForwardMessageProvider>
-  )
-}
-
-function ForwardButton() {
-  const { actions } = use(Composer.Context)
-  return <Button onPress={actions.submit}>Forward</Button>
-}
-```
-
-The ForwardButton lives outside the Composer.Frame but still has access to the
-submit action because it's within the provider. Even though it's a one-off
-component, it can still access the composer's state and actions from outside the
-UI itself.
-
-**Key insight:** Components that need shared state don't have to be visually
-nested inside each other—they just need to be within the same provider.

+ 0 - 3502
neo/.agents/skills/vercel-react-best-practices/AGENTS.md

@@ -1,3502 +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. 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 [Hoist Static I/O to Module Level](#34-hoist-static-io-to-module-level)
-   - 3.5 [Minimize Serialization at RSC Boundaries](#35-minimize-serialization-at-rsc-boundaries)
-   - 3.6 [Parallel Data Fetching with Component Composition](#36-parallel-data-fetching-with-component-composition)
-   - 3.7 [Parallel Nested Data Fetching](#37-parallel-nested-data-fetching)
-   - 3.8 [Per-Request Deduplication with React.cache()](#38-per-request-deduplication-with-reactcache)
-   - 3.9 [Use after() for Non-Blocking Operations](#39-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 [Don't Define Components Inside Components](#54-dont-define-components-inside-components)
-   - 5.5 [Extract Default Non-primitive Parameter Value from Memoized Component to Constant](#55-extract-default-non-primitive-parameter-value-from-memoized-component-to-constant)
-   - 5.6 [Extract to Memoized Components](#56-extract-to-memoized-components)
-   - 5.7 [Narrow Effect Dependencies](#57-narrow-effect-dependencies)
-   - 5.8 [Put Interaction Logic in Event Handlers](#58-put-interaction-logic-in-event-handlers)
-   - 5.9 [Split Combined Hook Computations](#59-split-combined-hook-computations)
-   - 5.10 [Subscribe to Derived State](#510-subscribe-to-derived-state)
-   - 5.11 [Use Functional setState Updates](#511-use-functional-setstate-updates)
-   - 5.12 [Use Lazy State Initialization](#512-use-lazy-state-initialization)
-   - 5.13 [Use Transitions for Non-Urgent Updates](#513-use-transitions-for-non-urgent-updates)
-   - 5.14 [Use useDeferredValue for Expensive Derived Renders](#514-use-usedeferredvalue-for-expensive-derived-renders)
-   - 5.15 [Use useRef for Transient Values](#515-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 defer or async on Script Tags](#68-use-defer-or-async-on-script-tags)
-   - 6.9 [Use Explicit Conditional Rendering](#69-use-explicit-conditional-rendering)
-   - 6.10 [Use React DOM Resource Hints](#610-use-react-dom-resource-hints)
-   - 6.11 [Use useTransition Over Manual Loading States](#611-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 [Defer Non-Critical Work with requestIdleCallback](#77-defer-non-critical-work-with-requestidlecallback)
-   - 7.8 [Early Length Check for Array Comparisons](#78-early-length-check-for-array-comparisons)
-   - 7.9 [Early Return from Functions](#79-early-return-from-functions)
-   - 7.10 [Hoist RegExp Creation](#710-hoist-regexp-creation)
-   - 7.11 [Use flatMap to Map and Filter in One Pass](#711-use-flatmap-to-map-and-filter-in-one-pass)
-   - 7.12 [Use Loop for Min/Max Instead of Sort](#712-use-loop-for-minmax-instead-of-sort)
-   - 7.13 [Use Set/Map for O(1) Lookups](#713-use-setmap-for-o1-lookups)
-   - 7.14 [Use toSorted() Instead of sort() for Immutability](#714-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 (
-    <div>
-      <div>Sidebar</div>
-      <div>Header</div>
-      <div>
-        <DataDisplay data={data} />
-      </div>
-      <div>Footer</div>
-    </div>
-  )
-}
-```
-
-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 (
-    <div>
-      <div>Sidebar</div>
-      <div>Header</div>
-      <div>
-        <Suspense fallback={<Skeleton />}>
-          <DataDisplay />
-        </Suspense>
-      </div>
-      <div>Footer</div>
-    </div>
-  )
-}
-
-async function DataDisplay() {
-  const data = await fetchData() // Only blocks this component
-  return <div>{data.content}</div>
-}
-```
-
-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 (
-    <div>
-      <div>Sidebar</div>
-      <div>Header</div>
-      <Suspense fallback={<Skeleton />}>
-        <DataDisplay dataPromise={dataPromise} />
-        <DataSummary dataPromise={dataPromise} />
-      </Suspense>
-      <div>Footer</div>
-    </div>
-  )
-}
-
-function DataDisplay({ dataPromise }: { dataPromise: Promise<Data> }) {
-  const data = use(dataPromise) // Unwraps the promise
-  return <div>{data.content}</div>
-}
-
-function DataSummary({ dataPromise }: { dataPromise: Promise<Data> }) {
-  const data = use(dataPromise) // Reuses the same promise
-  return <div>{data.summary}</div>
-}
-```
-
-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 - Next.js 13.5+ (recommended):**
-
-```tsx
-// Keep the standard imports - Next.js transforms them to direct imports
-import { Check, X, Menu } from 'lucide-react'
-// Full TypeScript support, no manual path wrangling
-```
-
-This is the recommended approach because it preserves TypeScript type safety and editor autocompletion while still eliminating the barrel import cost.
-
-**Correct - Direct imports (non-Next.js projects):**
-
-```tsx
-import Button from '@mui/material/Button'
-import TextField from '@mui/material/TextField'
-// Loads only what you use
-```
-
-> **TypeScript warning:** Some libraries (notably `lucide-react`) don't ship `.d.ts` files for their deep import paths. Importing from `lucide-react/dist/esm/icons/check` resolves to an implicit `any` type, causing errors under `strict` or `noImplicitAny`. Prefer `optimizePackageImports` when available, or verify the library exports types for its subpaths before using direct imports.
-
-These optimizations 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<React.SetStateAction<boolean>> }) {
-  const [frames, setFrames] = useState<Frame[] | null>(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 <Skeleton />
-  return <Canvas frames={frames} />
-}
-```
-
-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 (
-    <html>
-      <body>
-        {children}
-        <Analytics />
-      </body>
-    </html>
-  )
-}
-```
-
-**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 (
-    <html>
-      <body>
-        {children}
-        <Analytics />
-      </body>
-    </html>
-  )
-}
-```
-
-### 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 <MonacoEditor value={code} />
-}
-```
-
-**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 <MonacoEditor value={code} />
-}
-```
-
-### 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 (
-    <button
-      onMouseEnter={preload}
-      onFocus={preload}
-      onClick={onClick}
-    >
-      Open Editor
-    </button>
-  )
-}
-```
-
-**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 <FlagsContext.Provider value={flags}>
-    {children}
-  </FlagsContext.Provider>
-}
-```
-
-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)
-<ClientList usernames={usernames} usernamesOrdered={usernames.toSorted()} />
-```
-
-**Correct: sends 3 strings**
-
-```tsx
-// RSC: send once
-<ClientList usernames={usernames} />
-
-// 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
-<C users={users} active={users.filter(u => u.active)} />
-<C product={product} productName={product.name} />
-
-// ✅ Good
-<C users={users} />
-<C product={product} />
-// 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<string, any>({
-  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 Hoist Static I/O to Module Level
-
-**Impact: HIGH (avoids repeated file/network I/O per request)**
-
-When loading static assets (fonts, logos, images, config files) in route handlers or server functions, hoist the I/O operation to module level. Module-level code runs once when the module is first imported, not on every request. This eliminates redundant file system reads or network fetches that would otherwise run on every invocation.
-
-**Incorrect: reads font file on every request**
-
-**Correct: loads once at module initialization**
-
-**Alternative: synchronous file reads with Node.js fs**
-
-**General Node.js example: loading config or templates**
-
-**When to use this pattern:**
-
-- Loading fonts for OG image generation
-
-- Loading static logos, icons, or watermarks
-
-- Reading configuration files that don't change at runtime
-
-- Loading email templates or other static templates
-
-- Any static asset that's the same across all requests
-
-**When NOT to use this pattern:**
-
-- Assets that vary per request or user
-
-- Files that may change during runtime (use caching with TTL instead)
-
-- Large files that would consume too much memory if kept loaded
-
-- Sensitive data that shouldn't persist in memory
-
-**With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute):** Module-level caching is especially effective because multiple concurrent requests share the same function instance. The static assets stay loaded in memory across requests without cold start penalties.
-
-**In traditional serverless:** Each cold start re-executes module-level code, but subsequent warm invocations reuse the loaded assets until the instance is recycled.
-
-### 3.5 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 <Profile user={user} />
-}
-
-'use client'
-function Profile({ user }: { user: User }) {
-  return <div>{user.name}</div>  // uses 1 field
-}
-```
-
-**Correct: serializes only 1 field**
-
-```tsx
-async function Page() {
-  const user = await fetchUser()
-  return <Profile name={user.name} />
-}
-
-'use client'
-function Profile({ name }: { name: string }) {
-  return <div>{name}</div>
-}
-```
-
-### 3.6 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 (
-    <div>
-      <div>{header}</div>
-      <Sidebar />
-    </div>
-  )
-}
-
-async function Sidebar() {
-  const items = await fetchSidebarItems()
-  return <nav>{items.map(renderItem)}</nav>
-}
-```
-
-**Correct: both fetch simultaneously**
-
-```tsx
-async function Header() {
-  const data = await fetchHeader()
-  return <div>{data}</div>
-}
-
-async function Sidebar() {
-  const items = await fetchSidebarItems()
-  return <nav>{items.map(renderItem)}</nav>
-}
-
-export default function Page() {
-  return (
-    <div>
-      <Header />
-      <Sidebar />
-    </div>
-  )
-}
-```
-
-**Alternative with children prop:**
-
-```tsx
-async function Header() {
-  const data = await fetchHeader()
-  return <div>{data}</div>
-}
-
-async function Sidebar() {
-  const items = await fetchSidebarItems()
-  return <nav>{items.map(renderItem)}</nav>
-}
-
-function Layout({ children }: { children: ReactNode }) {
-  return (
-    <div>
-      <Header />
-      {children}
-    </div>
-  )
-}
-
-export default function Page() {
-  return (
-    <Layout>
-      <Sidebar />
-    </Layout>
-  )
-}
-```
-
-### 3.7 Parallel Nested Data Fetching
-
-**Impact: CRITICAL (eliminates server-side waterfalls)**
-
-When fetching nested data in parallel, chain dependent fetches within each item's promise so a slow item doesn't block the rest.
-
-**Incorrect: a single slow item blocks all nested fetches**
-
-```tsx
-const chats = await Promise.all(
-  chatIds.map(id => getChat(id))
-)
-
-const chatAuthors = await Promise.all(
-  chats.map(chat => getUser(chat.author))
-)
-```
-
-If one `getChat(id)` out of 100 is extremely slow, the authors of the other 99 chats can't start loading even though their data is ready.
-
-**Correct: each item chains its own nested fetch**
-
-```tsx
-const chatAuthors = await Promise.all(
-  chatIds.map(id => getChat(id).then(chat => getUser(chat.author)))
-)
-```
-
-Each item independently chains `getChat` → `getUser`, so a slow chat doesn't block author fetches for the others.
-
-### 3.8 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.9 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<string, Set<() => 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 <button onClick={() => trigger()}>Update</button>
-}
-```
-
-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 <p>{fullName}</p>
-}
-```
-
-**Correct: derive during render**
-
-```tsx
-function Form() {
-  const [firstName, setFirstName] = useState('First')
-  const [lastName, setLastName] = useState('Last')
-  const fullName = firstName + ' ' + lastName
-
-  return <p>{fullName}</p>
-}
-```
-
-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 <button onClick={handleShare}>Share</button>
-}
-```
-
-**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 <button onClick={handleShare}>Share</button>
-}
-```
-
-### 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 <Skeleton />
-  // return some markup
-}
-```
-
-**Correct:**
-
-```tsx
-function Header({ user, notifications }: Props) {
-  const isLoading = user.isLoading || notifications.isLoading
-
-  if (isLoading) return <Skeleton />
-  // return some markup
-}
-```
-
-### 5.4 Don't Define Components Inside Components
-
-**Impact: HIGH (prevents remount on every render)**
-
-Defining a component inside another component creates a new component type on every render. React sees a different component each time and fully remounts it, destroying all state and DOM.
-
-A common reason developers do this is to access parent variables without passing props. Always pass props instead.
-
-**Incorrect: remounts on every render**
-
-```tsx
-function UserProfile({ user, theme }) {
-  // Defined inside to access `theme` - BAD
-  const Avatar = () => (
-    <img
-      src={user.avatarUrl}
-      className={theme === 'dark' ? 'avatar-dark' : 'avatar-light'}
-    />
-  )
-
-  // Defined inside to access `user` - BAD
-  const Stats = () => (
-    <div>
-      <span>{user.followers} followers</span>
-      <span>{user.posts} posts</span>
-    </div>
-  )
-
-  return (
-    <div>
-      <Avatar />
-      <Stats />
-    </div>
-  )
-}
-```
-
-Every time `UserProfile` renders, `Avatar` and `Stats` are new component types. React unmounts the old instances and mounts new ones, losing any internal state, running effects again, and recreating DOM nodes.
-
-**Correct: pass props instead**
-
-```tsx
-function Avatar({ src, theme }: { src: string; theme: string }) {
-  return (
-    <img
-      src={src}
-      className={theme === 'dark' ? 'avatar-dark' : 'avatar-light'}
-    />
-  )
-}
-
-function Stats({ followers, posts }: { followers: number; posts: number }) {
-  return (
-    <div>
-      <span>{followers} followers</span>
-      <span>{posts} posts</span>
-    </div>
-  )
-}
-
-function UserProfile({ user, theme }) {
-  return (
-    <div>
-      <Avatar src={user.avatarUrl} theme={theme} />
-      <Stats followers={user.followers} posts={user.posts} />
-    </div>
-  )
-}
-```
-
-**Symptoms of this bug:**
-
-- Input fields lose focus on every keystroke
-
-- Animations restart unexpectedly
-
-- `useEffect` cleanup/setup runs on every parent render
-
-- Scroll position resets inside the component
-
-### 5.5 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
-<UserAvatar />
-```
-
-**Correct: stable default value**
-
-```tsx
-const NOOP = () => {};
-
-const UserAvatar = memo(function UserAvatar({ onClick = NOOP }: { onClick?: () => void }) {
-  // ...
-})
-
-// Used without optional onClick
-<UserAvatar />
-```
-
-### 5.6 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 <Avatar id={id} />
-  }, [user])
-
-  if (loading) return <Skeleton />
-  return <div>{avatar}</div>
-}
-```
-
-**Correct: skips computation when loading**
-
-```tsx
-const UserAvatar = memo(function UserAvatar({ user }: { user: User }) {
-  const id = useMemo(() => computeAvatarId(user), [user])
-  return <Avatar id={id} />
-})
-
-function Profile({ user, loading }: Props) {
-  if (loading) return <Skeleton />
-  return (
-    <div>
-      <UserAvatar user={user} />
-    </div>
-  )
-}
-```
-
-**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.7 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.8 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 <button onClick={() => setSubmitted(true)}>Submit</button>
-}
-```
-
-**Correct: do it in the handler**
-
-```tsx
-function Form() {
-  const theme = useContext(ThemeContext)
-
-  function handleSubmit() {
-    post('/api/register')
-    showToast('Registered', theme)
-  }
-
-  return <button onClick={handleSubmit}>Submit</button>
-}
-```
-
-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.9 Split Combined Hook Computations
-
-**Impact: MEDIUM (avoids recomputing independent steps)**
-
-When a hook contains multiple independent tasks with different dependencies, split them into separate hooks. A combined hook reruns all tasks when any dependency changes, even if some tasks don't use the changed value.
-
-**Incorrect: changing `sortOrder` recomputes filtering**
-
-```tsx
-const sortedProducts = useMemo(() => {
-  const filtered = products.filter((p) => p.category === category)
-  const sorted = filtered.toSorted((a, b) =>
-    sortOrder === "asc" ? a.price - b.price : b.price - a.price
-  )
-  return sorted
-}, [products, category, sortOrder])
-```
-
-**Correct: filtering only recomputes when products or category change**
-
-```tsx
-const filteredProducts = useMemo(
-  () => products.filter((p) => p.category === category),
-  [products, category]
-)
-
-const sortedProducts = useMemo(
-  () =>
-    filteredProducts.toSorted((a, b) =>
-      sortOrder === "asc" ? a.price - b.price : b.price - a.price
-    ),
-  [filteredProducts, sortOrder]
-)
-```
-
-This pattern also applies to `useEffect` when combining unrelated side effects:
-
-**Incorrect: both effects run when either dependency changes**
-
-```tsx
-useEffect(() => {
-  analytics.trackPageView(pathname)
-  document.title = `${pageTitle} | My App`
-}, [pathname, pageTitle])
-```
-
-**Correct: effects run independently**
-
-```tsx
-useEffect(() => {
-  analytics.trackPageView(pathname)
-}, [pathname])
-
-useEffect(() => {
-  document.title = `${pageTitle} | My App`
-}, [pageTitle])
-```
-
-**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, it automatically optimizes dependency tracking and may handle some of these cases for you.
-
-### 5.10 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 <nav className={isMobile ? 'mobile' : 'desktop'} />
-}
-```
-
-**Correct: re-renders only when boolean changes**
-
-```tsx
-function Sidebar() {
-  const isMobile = useMediaQuery('(max-width: 767px)')
-  return <nav className={isMobile ? 'mobile' : 'desktop'} />
-}
-```
-
-### 5.11 Use Functional setState Updates
-
-**Impact: MEDIUM (prevents stale closures and unnecessary callback recreations)**
-
-When updating state based on the current state value, use the functional update form of setState instead of directly referencing the state variable. This prevents stale closures, eliminates unnecessary dependencies, and creates stable callback references.
-
-**Incorrect: requires state as dependency**
-
-```tsx
-function TodoList() {
-  const [items, setItems] = useState(initialItems)
-  
-  // Callback must depend on items, recreated on every items change
-  const addItems = useCallback((newItems: Item[]) => {
-    setItems([...items, ...newItems])
-  }, [items])  // ❌ items dependency causes recreations
-  
-  // Risk of stale closure if dependency is forgotten
-  const removeItem = useCallback((id: string) => {
-    setItems(items.filter(item => item.id !== id))
-  }, [])  // ❌ Missing items dependency - will use stale items!
-  
-  return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />
-}
-```
-
-The first callback is recreated every time `items` changes, which can cause child components to re-render unnecessarily. The second callback has a stale closure bug—it will always reference the initial `items` value.
-
-**Correct: stable callbacks, no stale closures**
-
-```tsx
-function TodoList() {
-  const [items, setItems] = useState(initialItems)
-  
-  // Stable callback, never recreated
-  const addItems = useCallback((newItems: Item[]) => {
-    setItems(curr => [...curr, ...newItems])
-  }, [])  // ✅ No dependencies needed
-  
-  // Always uses latest state, no stale closure risk
-  const removeItem = useCallback((id: string) => {
-    setItems(curr => curr.filter(item => item.id !== id))
-  }, [])  // ✅ Safe and stable
-  
-  return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />
-}
-```
-
-**Benefits:**
-
-1. **Stable callback references** - Callbacks don't need to be recreated when state changes
-
-2. **No stale closures** - Always operates on the latest state value
-
-3. **Fewer dependencies** - Simplifies dependency arrays and reduces memory leaks
-
-4. **Prevents bugs** - Eliminates the most common source of React closure bugs
-
-**When to use functional updates:**
-
-- Any setState that depends on the current state value
-
-- Inside useCallback/useMemo when state is needed
-
-- Event handlers that reference state
-
-- Async operations that update state
-
-**When direct updates are fine:**
-
-- Setting state to a static value: `setCount(0)`
-
-- Setting state from props/arguments only: `setName(newName)`
-
-- State doesn't depend on previous value
-
-**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler can automatically optimize some cases, but functional updates are still recommended for correctness and to prevent stale closure bugs.
-
-### 5.12 Use Lazy State Initialization
-
-**Impact: MEDIUM (wasted computation on every render)**
-
-Pass a function to `useState` for expensive initial values. Without the function form, the initializer runs on every render even though the value is only used once.
-
-**Incorrect: runs on every render**
-
-```tsx
-function FilteredList({ items }: { items: Item[] }) {
-  // buildSearchIndex() runs on EVERY render, even after initialization
-  const [searchIndex, setSearchIndex] = useState(buildSearchIndex(items))
-  const [query, setQuery] = useState('')
-  
-  // When query changes, buildSearchIndex runs again unnecessarily
-  return <SearchResults index={searchIndex} query={query} />
-}
-
-function UserProfile() {
-  // JSON.parse runs on every render
-  const [settings, setSettings] = useState(
-    JSON.parse(localStorage.getItem('settings') || '{}')
-  )
-  
-  return <SettingsForm settings={settings} onChange={setSettings} />
-}
-```
-
-**Correct: runs only once**
-
-```tsx
-function FilteredList({ items }: { items: Item[] }) {
-  // buildSearchIndex() runs ONLY on initial render
-  const [searchIndex, setSearchIndex] = useState(() => buildSearchIndex(items))
-  const [query, setQuery] = useState('')
-  
-  return <SearchResults index={searchIndex} query={query} />
-}
-
-function UserProfile() {
-  // JSON.parse runs only on initial render
-  const [settings, setSettings] = useState(() => {
-    const stored = localStorage.getItem('settings')
-    return stored ? JSON.parse(stored) : {}
-  })
-  
-  return <SettingsForm settings={settings} onChange={setSettings} />
-}
-```
-
-Use lazy initialization when computing initial values from localStorage/sessionStorage, building data structures (indexes, maps), reading from the DOM, or performing heavy transformations.
-
-For simple primitives (`useState(0)`), direct references (`useState(props.value)`), or cheap literals (`useState({})`), the function form is unnecessary.
-
-### 5.13 Use Transitions for Non-Urgent Updates
-
-**Impact: MEDIUM (maintains UI responsiveness)**
-
-Mark frequent, non-urgent state updates as transitions to maintain UI responsiveness.
-
-**Incorrect: blocks UI on every scroll**
-
-```tsx
-function ScrollTracker() {
-  const [scrollY, setScrollY] = useState(0)
-  useEffect(() => {
-    const handler = () => setScrollY(window.scrollY)
-    window.addEventListener('scroll', handler, { passive: true })
-    return () => window.removeEventListener('scroll', handler)
-  }, [])
-}
-```
-
-**Correct: non-blocking updates**
-
-```tsx
-import { startTransition } from 'react'
-
-function ScrollTracker() {
-  const [scrollY, setScrollY] = useState(0)
-  useEffect(() => {
-    const handler = () => {
-      startTransition(() => setScrollY(window.scrollY))
-    }
-    window.addEventListener('scroll', handler, { passive: true })
-    return () => window.removeEventListener('scroll', handler)
-  }, [])
-}
-```
-
-### 5.14 Use useDeferredValue for Expensive Derived Renders
-
-**Impact: MEDIUM (keeps input responsive during heavy computation)**
-
-When user input triggers expensive computations or renders, use `useDeferredValue` to keep the input responsive. The deferred value lags behind, allowing React to prioritize the input update and render the expensive result when idle.
-
-**Incorrect: input feels laggy while filtering**
-
-```tsx
-function Search({ items }: { items: Item[] }) {
-  const [query, setQuery] = useState('')
-  const filtered = items.filter(item => fuzzyMatch(item, query))
-
-  return (
-    <>
-      <input value={query} onChange={e => setQuery(e.target.value)} />
-      <ResultsList results={filtered} />
-    </>
-  )
-}
-```
-
-**Correct: input stays snappy, results render when ready**
-
-```tsx
-function Search({ items }: { items: Item[] }) {
-  const [query, setQuery] = useState('')
-  const deferredQuery = useDeferredValue(query)
-  const filtered = useMemo(
-    () => items.filter(item => fuzzyMatch(item, deferredQuery)),
-    [items, deferredQuery]
-  )
-  const isStale = query !== deferredQuery
-
-  return (
-    <>
-      <input value={query} onChange={e => setQuery(e.target.value)} />
-      <div style={{ opacity: isStale ? 0.7 : 1 }}>
-        <ResultsList results={filtered} />
-      </div>
-    </>
-  )
-}
-```
-
-**When to use:**
-
-- Filtering/searching large lists
-
-- Expensive visualizations (charts, graphs) reacting to input
-
-- Any derived state that causes noticeable render delays
-
-**Note:** Wrap the expensive computation in `useMemo` with the deferred value as a dependency, otherwise it still runs on every render.
-
-Reference: [https://react.dev/reference/react/useDeferredValue](https://react.dev/reference/react/useDeferredValue)
-
-### 5.15 Use useRef for Transient Values
-
-**Impact: MEDIUM (avoids unnecessary re-renders on frequent updates)**
-
-When a value changes frequently and you don't want a re-render on every update (e.g., mouse trackers, intervals, transient flags), store it in `useRef` instead of `useState`. Keep component state for UI; use refs for temporary DOM-adjacent values. Updating a ref does not trigger a re-render.
-
-**Incorrect: renders every update**
-
-```tsx
-function Tracker() {
-  const [lastX, setLastX] = useState(0)
-
-  useEffect(() => {
-    const onMove = (e: MouseEvent) => setLastX(e.clientX)
-    window.addEventListener('mousemove', onMove)
-    return () => window.removeEventListener('mousemove', onMove)
-  }, [])
-
-  return (
-    <div
-      style={{
-        position: 'fixed',
-        top: 0,
-        left: lastX,
-        width: 8,
-        height: 8,
-        background: 'black',
-      }}
-    />
-  )
-}
-```
-
-**Correct: no re-render for tracking**
-
-```tsx
-function Tracker() {
-  const lastXRef = useRef(0)
-  const dotRef = useRef<HTMLDivElement>(null)
-
-  useEffect(() => {
-    const onMove = (e: MouseEvent) => {
-      lastXRef.current = e.clientX
-      const node = dotRef.current
-      if (node) {
-        node.style.transform = `translateX(${e.clientX}px)`
-      }
-    }
-    window.addEventListener('mousemove', onMove)
-    return () => window.removeEventListener('mousemove', onMove)
-  }, [])
-
-  return (
-    <div
-      ref={dotRef}
-      style={{
-        position: 'fixed',
-        top: 0,
-        left: 0,
-        width: 8,
-        height: 8,
-        background: 'black',
-        transform: 'translateX(0px)',
-      }}
-    />
-  )
-}
-```
-
----
-
-## 6. Rendering Performance
-
-**Impact: MEDIUM**
-
-Optimizing the rendering process reduces the work the browser needs to do.
-
-### 6.1 Animate SVG Wrapper Instead of SVG Element
-
-**Impact: LOW (enables hardware acceleration)**
-
-Many browsers don't have hardware acceleration for CSS3 animations on SVG elements. Wrap SVG in a `<div>` and animate the wrapper instead.
-
-**Incorrect: animating SVG directly - no hardware acceleration**
-
-```tsx
-function LoadingSpinner() {
-  return (
-    <svg 
-      className="animate-spin"
-      width="24" 
-      height="24" 
-      viewBox="0 0 24 24"
-    >
-      <circle cx="12" cy="12" r="10" stroke="currentColor" />
-    </svg>
-  )
-}
-```
-
-**Correct: animating wrapper div - hardware accelerated**
-
-```tsx
-function LoadingSpinner() {
-  return (
-    <div className="animate-spin">
-      <svg 
-        width="24" 
-        height="24" 
-        viewBox="0 0 24 24"
-      >
-        <circle cx="12" cy="12" r="10" stroke="currentColor" />
-      </svg>
-    </div>
-  )
-}
-```
-
-This applies to all CSS transforms and transitions (`transform`, `opacity`, `translate`, `scale`, `rotate`). The wrapper div allows browsers to use GPU acceleration for smoother animations.
-
-### 6.2 CSS content-visibility for Long Lists
-
-**Impact: HIGH (faster initial render)**
-
-Apply `content-visibility: auto` to defer off-screen rendering.
-
-**CSS:**
-
-```css
-.message-item {
-  content-visibility: auto;
-  contain-intrinsic-size: 0 80px;
-}
-```
-
-**Example:**
-
-```tsx
-function MessageList({ messages }: { messages: Message[] }) {
-  return (
-    <div className="overflow-y-auto h-screen">
-      {messages.map(msg => (
-        <div key={msg.id} className="message-item">
-          <Avatar user={msg.author} />
-          <div>{msg.content}</div>
-        </div>
-      ))}
-    </div>
-  )
-}
-```
-
-For 1000 messages, browser skips layout/paint for ~990 off-screen items (10× faster initial render).
-
-### 6.3 Hoist Static JSX Elements
-
-**Impact: LOW (avoids re-creation)**
-
-Extract static JSX outside components to avoid re-creation.
-
-**Incorrect: recreates element every render**
-
-```tsx
-function LoadingSkeleton() {
-  return <div className="animate-pulse h-20 bg-gray-200" />
-}
-
-function Container() {
-  return (
-    <div>
-      {loading && <LoadingSkeleton />}
-    </div>
-  )
-}
-```
-
-**Correct: reuses same element**
-
-```tsx
-const loadingSkeleton = (
-  <div className="animate-pulse h-20 bg-gray-200" />
-)
-
-function Container() {
-  return (
-    <div>
-      {loading && loadingSkeleton}
-    </div>
-  )
-}
-```
-
-This is especially helpful for large and static SVG nodes, which can be expensive to recreate on every render.
-
-**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler automatically hoists static JSX elements and optimizes component re-renders, making manual hoisting unnecessary.
-
-### 6.4 Optimize SVG Precision
-
-**Impact: LOW (reduces file size)**
-
-Reduce SVG coordinate precision to decrease file size. The optimal precision depends on the viewBox size, but in general reducing precision should be considered.
-
-**Incorrect: excessive precision**
-
-```svg
-<path d="M 10.293847 20.847362 L 30.938472 40.192837" />
-```
-
-**Correct: 1 decimal place**
-
-```svg
-<path d="M 10.3 20.8 L 30.9 40.2" />
-```
-
-**Automate with SVGO:**
-
-```bash
-npx svgo --precision=1 --multipass icon.svg
-```
-
-### 6.5 Prevent Hydration Mismatch Without Flickering
-
-**Impact: MEDIUM (avoids visual flicker and hydration errors)**
-
-When rendering content that depends on client-side storage (localStorage, cookies), avoid both SSR breakage and post-hydration flickering by injecting a synchronous script that updates the DOM before React hydrates.
-
-**Incorrect: breaks SSR**
-
-```tsx
-function ThemeWrapper({ children }: { children: ReactNode }) {
-  // localStorage is not available on server - throws error
-  const theme = localStorage.getItem('theme') || 'light'
-  
-  return (
-    <div className={theme}>
-      {children}
-    </div>
-  )
-}
-```
-
-Server-side rendering will fail because `localStorage` is undefined.
-
-**Incorrect: visual flickering**
-
-```tsx
-function ThemeWrapper({ children }: { children: ReactNode }) {
-  const [theme, setTheme] = useState('light')
-  
-  useEffect(() => {
-    // Runs after hydration - causes visible flash
-    const stored = localStorage.getItem('theme')
-    if (stored) {
-      setTheme(stored)
-    }
-  }, [])
-  
-  return (
-    <div className={theme}>
-      {children}
-    </div>
-  )
-}
-```
-
-Component first renders with default value (`light`), then updates after hydration, causing a visible flash of incorrect content.
-
-**Correct: no flicker, no hydration mismatch**
-
-```tsx
-function ThemeWrapper({ children }: { children: ReactNode }) {
-  return (
-    <>
-      <div id="theme-wrapper">
-        {children}
-      </div>
-      <script
-        dangerouslySetInnerHTML={{
-          __html: `
-            (function() {
-              try {
-                var theme = localStorage.getItem('theme') || 'light';
-                var el = document.getElementById('theme-wrapper');
-                if (el) el.className = theme;
-              } catch (e) {}
-            })();
-          `,
-        }}
-      />
-    </>
-  )
-}
-```
-
-The inline script executes synchronously before showing the element, ensuring the DOM already has the correct value. No flickering, no hydration mismatch.
-
-This pattern is especially useful for theme toggles, user preferences, authentication states, and any client-only data that should render immediately without flashing default values.
-
-### 6.6 Suppress Expected Hydration Mismatches
-
-**Impact: LOW-MEDIUM (avoids noisy hydration warnings for known differences)**
-
-In SSR frameworks (e.g., Next.js), some values are intentionally different on server vs client (random IDs, dates, locale/timezone formatting). For these *expected* mismatches, wrap the dynamic text in an element with `suppressHydrationWarning` to prevent noisy warnings. Do not use this to hide real bugs. Don’t overuse it.
-
-**Incorrect: known mismatch warnings**
-
-```tsx
-function Timestamp() {
-  return <span>{new Date().toLocaleString()}</span>
-}
-```
-
-**Correct: suppress expected mismatch only**
-
-```tsx
-function Timestamp() {
-  return (
-    <span suppressHydrationWarning>
-      {new Date().toLocaleString()}
-    </span>
-  )
-}
-```
-
-### 6.7 Use Activity Component for Show/Hide
-
-**Impact: MEDIUM (preserves state/DOM)**
-
-Use React's `<Activity>` to preserve state/DOM for expensive components that frequently toggle visibility.
-
-**Usage:**
-
-```tsx
-import { Activity } from 'react'
-
-function Dropdown({ isOpen }: Props) {
-  return (
-    <Activity mode={isOpen ? 'visible' : 'hidden'}>
-      <ExpensiveMenu />
-    </Activity>
-  )
-}
-```
-
-Avoids expensive re-renders and state loss.
-
-### 6.8 Use defer or async on Script Tags
-
-**Impact: HIGH (eliminates render-blocking)**
-
-Script tags without `defer` or `async` block HTML parsing while the script downloads and executes. This delays First Contentful Paint and Time to Interactive.
-
-- **`defer`**: Downloads in parallel, executes after HTML parsing completes, maintains execution order
-
-- **`async`**: Downloads in parallel, executes immediately when ready, no guaranteed order
-
-Use `defer` for scripts that depend on DOM or other scripts. Use `async` for independent scripts like analytics.
-
-**Incorrect: blocks rendering**
-
-```tsx
-export default function Document() {
-  return (
-    <html>
-      <head>
-        <script src="https://example.com/analytics.js" />
-        <script src="/scripts/utils.js" />
-      </head>
-      <body>{/* content */}</body>
-    </html>
-  )
-}
-```
-
-**Correct: non-blocking**
-
-```tsx
-import Script from 'next/script'
-
-export default function Page() {
-  return (
-    <>
-      <Script src="https://example.com/analytics.js" strategy="afterInteractive" />
-      <Script src="/scripts/utils.js" strategy="beforeInteractive" />
-    </>
-  )
-}
-```
-
-**Note:** In Next.js, prefer the `next/script` component with `strategy` prop instead of raw script tags:
-
-Reference: [https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#defer](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#defer)
-
-### 6.9 Use Explicit Conditional Rendering
-
-**Impact: LOW (prevents rendering 0 or NaN)**
-
-Use explicit ternary operators (`? :`) instead of `&&` for conditional rendering when the condition can be `0`, `NaN`, or other falsy values that render.
-
-**Incorrect: renders "0" when count is 0**
-
-```tsx
-function Badge({ count }: { count: number }) {
-  return (
-    <div>
-      {count && <span className="badge">{count}</span>}
-    </div>
-  )
-}
-
-// When count = 0, renders: <div>0</div>
-// When count = 5, renders: <div><span class="badge">5</span></div>
-```
-
-**Correct: renders nothing when count is 0**
-
-```tsx
-function Badge({ count }: { count: number }) {
-  return (
-    <div>
-      {count > 0 ? <span className="badge">{count}</span> : null}
-    </div>
-  )
-}
-
-// When count = 0, renders: <div></div>
-// When count = 5, renders: <div><span class="badge">5</span></div>
-```
-
-### 6.10 Use React DOM Resource Hints
-
-**Impact: HIGH (reduces load time for critical resources)**
-
-React DOM provides APIs to hint the browser about resources it will need. These are especially useful in server components to start loading resources before the client even receives the HTML.
-
-- **`prefetchDNS(href)`**: Resolve DNS for a domain you expect to connect to
-
-- **`preconnect(href)`**: Establish connection (DNS + TCP + TLS) to a server
-
-- **`preload(href, options)`**: Fetch a resource (stylesheet, font, script, image) you'll use soon
-
-- **`preloadModule(href)`**: Fetch an ES module you'll use soon
-
-- **`preinit(href, options)`**: Fetch and evaluate a stylesheet or script
-
-- **`preinitModule(href)`**: Fetch and evaluate an ES module
-
-**Example: preconnect to third-party APIs**
-
-```tsx
-import { preconnect, prefetchDNS } from 'react-dom'
-
-export default function App() {
-  prefetchDNS('https://analytics.example.com')
-  preconnect('https://api.example.com')
-
-  return <main>{/* content */}</main>
-}
-```
-
-**Example: preload critical fonts and styles**
-
-```tsx
-import { preload, preinit } from 'react-dom'
-
-export default function RootLayout({ children }) {
-  // Preload font file
-  preload('/fonts/inter.woff2', { as: 'font', type: 'font/woff2', crossOrigin: 'anonymous' })
-
-  // Fetch and apply critical stylesheet immediately
-  preinit('/styles/critical.css', { as: 'style' })
-
-  return (
-    <html>
-      <body>{children}</body>
-    </html>
-  )
-}
-```
-
-**Example: preload modules for code-split routes**
-
-```tsx
-import { preloadModule, preinitModule } from 'react-dom'
-
-function Navigation() {
-  const preloadDashboard = () => {
-    preloadModule('/dashboard.js', { as: 'script' })
-  }
-
-  return (
-    <nav>
-      <a href="/dashboard" onMouseEnter={preloadDashboard}>
-        Dashboard
-      </a>
-    </nav>
-  )
-}
-```
-
-**When to use each:**
-
-| API | Use case |
-
-|-----|----------|
-
-| `prefetchDNS` | Third-party domains you'll connect to later |
-
-| `preconnect` | APIs or CDNs you'll fetch from immediately |
-
-| `preload` | Critical resources needed for current page |
-
-| `preloadModule` | JS modules for likely next navigation |
-
-| `preinit` | Stylesheets/scripts that must execute early |
-
-| `preinitModule` | ES modules that must execute early |
-
-Reference: [https://react.dev/reference/react-dom#resource-preloading-apis](https://react.dev/reference/react-dom#resource-preloading-apis)
-
-### 6.11 Use useTransition Over Manual Loading States
-
-**Impact: LOW (reduces re-renders and improves code clarity)**
-
-Use `useTransition` instead of manual `useState` for loading states. This provides built-in `isPending` state and automatically manages transitions.
-
-**Incorrect: manual loading state**
-
-```tsx
-function SearchResults() {
-  const [query, setQuery] = useState('')
-  const [results, setResults] = useState([])
-  const [isLoading, setIsLoading] = useState(false)
-
-  const handleSearch = async (value: string) => {
-    setIsLoading(true)
-    setQuery(value)
-    const data = await fetchResults(value)
-    setResults(data)
-    setIsLoading(false)
-  }
-
-  return (
-    <>
-      <input onChange={(e) => handleSearch(e.target.value)} />
-      {isLoading && <Spinner />}
-      <ResultsList results={results} />
-    </>
-  )
-}
-```
-
-**Correct: useTransition with built-in pending state**
-
-```tsx
-import { useTransition, useState } from 'react'
-
-function SearchResults() {
-  const [query, setQuery] = useState('')
-  const [results, setResults] = useState([])
-  const [isPending, startTransition] = useTransition()
-
-  const handleSearch = (value: string) => {
-    setQuery(value) // Update input immediately
-    
-    startTransition(async () => {
-      // Fetch and update results
-      const data = await fetchResults(value)
-      setResults(data)
-    })
-  }
-
-  return (
-    <>
-      <input onChange={(e) => handleSearch(e.target.value)} />
-      {isPending && <Spinner />}
-      <ResultsList results={results} />
-    </>
-  )
-}
-```
-
-**Benefits:**
-
-- **Automatic pending state**: No need to manually manage `setIsLoading(true/false)`
-
-- **Error resilience**: Pending state correctly resets even if the transition throws
-
-- **Better responsiveness**: Keeps the UI responsive during updates
-
-- **Interrupt handling**: New transitions automatically cancel pending ones
-
-Reference: [https://react.dev/reference/react/useTransition](https://react.dev/reference/react/useTransition)
-
----
-
-## 7. JavaScript Performance
-
-**Impact: LOW-MEDIUM**
-
-Micro-optimizations for hot paths can add up to meaningful improvements.
-
-### 7.1 Avoid Layout Thrashing
-
-**Impact: MEDIUM (prevents forced synchronous layouts and reduces performance bottlenecks)**
-
-Avoid interleaving style writes with layout reads. When you read a layout property (like `offsetWidth`, `getBoundingClientRect()`, or `getComputedStyle()`) between style changes, the browser is forced to trigger a synchronous reflow.
-
-**This is OK: browser batches style changes**
-
-```typescript
-function updateElementStyles(element: HTMLElement) {
-  // Each line invalidates style, but browser batches the recalculation
-  element.style.width = '100px'
-  element.style.height = '200px'
-  element.style.backgroundColor = 'blue'
-  element.style.border = '1px solid black'
-}
-```
-
-**Incorrect: interleaved reads and writes force reflows**
-
-```typescript
-function layoutThrashing(element: HTMLElement) {
-  element.style.width = '100px'
-  const width = element.offsetWidth  // Forces reflow
-  element.style.height = '200px'
-  const height = element.offsetHeight  // Forces another reflow
-}
-```
-
-**Correct: batch writes, then read once**
-
-```typescript
-function updateElementStyles(element: HTMLElement) {
-  // Batch all writes together
-  element.style.width = '100px'
-  element.style.height = '200px'
-  element.style.backgroundColor = 'blue'
-  element.style.border = '1px solid black'
-  
-  // Read after all writes are done (single reflow)
-  const { width, height } = element.getBoundingClientRect()
-}
-```
-
-**Correct: batch reads, then writes**
-
-```typescript
-function updateElementStyles(element: HTMLElement) {
-  element.classList.add('highlighted-box')
-  
-  const { width, height } = element.getBoundingClientRect()
-}
-```
-
-**Better: use CSS classes**
-
-**React example:**
-
-```tsx
-// Incorrect: interleaving style changes with layout queries
-function Box({ isHighlighted }: { isHighlighted: boolean }) {
-  const ref = useRef<HTMLDivElement>(null)
-  
-  useEffect(() => {
-    if (ref.current && isHighlighted) {
-      ref.current.style.width = '100px'
-      const width = ref.current.offsetWidth // Forces layout
-      ref.current.style.height = '200px'
-    }
-  }, [isHighlighted])
-  
-  return <div ref={ref}>Content</div>
-}
-
-// Correct: toggle class
-function Box({ isHighlighted }: { isHighlighted: boolean }) {
-  return (
-    <div className={isHighlighted ? 'highlighted-box' : ''}>
-      Content
-    </div>
-  )
-}
-```
-
-Prefer CSS classes over inline styles when possible. CSS files are cached by the browser, and classes provide better separation of concerns and are easier to maintain.
-
-See [this gist](https://gist.github.com/paulirish/5d52fb081b3570c81e3a) and [CSS Triggers](https://csstriggers.com/) for more information on layout-forcing operations.
-
-### 7.2 Build Index Maps for Repeated Lookups
-
-**Impact: LOW-MEDIUM (1M ops to 2K ops)**
-
-Multiple `.find()` calls by the same key should use a Map.
-
-**Incorrect (O(n) per lookup):**
-
-```typescript
-function processOrders(orders: Order[], users: User[]) {
-  return orders.map(order => ({
-    ...order,
-    user: users.find(u => u.id === order.userId)
-  }))
-}
-```
-
-**Correct (O(1) per lookup):**
-
-```typescript
-function processOrders(orders: Order[], users: User[]) {
-  const userById = new Map(users.map(u => [u.id, u]))
-
-  return orders.map(order => ({
-    ...order,
-    user: userById.get(order.userId)
-  }))
-}
-```
-
-Build map once (O(n)), then all lookups are O(1).
-
-For 1000 orders × 1000 users: 1M ops → 2K ops.
-
-### 7.3 Cache Property Access in Loops
-
-**Impact: LOW-MEDIUM (reduces lookups)**
-
-Cache object property lookups in hot paths.
-
-**Incorrect: 3 lookups × N iterations**
-
-```typescript
-for (let i = 0; i < arr.length; i++) {
-  process(obj.config.settings.value)
-}
-```
-
-**Correct: 1 lookup total**
-
-```typescript
-const value = obj.config.settings.value
-const len = arr.length
-for (let i = 0; i < len; i++) {
-  process(value)
-}
-```
-
-### 7.4 Cache Repeated Function Calls
-
-**Impact: MEDIUM (avoid redundant computation)**
-
-Use a module-level Map to cache function results when the same function is called repeatedly with the same inputs during render.
-
-**Incorrect: redundant computation**
-
-```typescript
-function ProjectList({ projects }: { projects: Project[] }) {
-  return (
-    <div>
-      {projects.map(project => {
-        // slugify() called 100+ times for same project names
-        const slug = slugify(project.name)
-        
-        return <ProjectCard key={project.id} slug={slug} />
-      })}
-    </div>
-  )
-}
-```
-
-**Correct: cached results**
-
-```typescript
-// Module-level cache
-const slugifyCache = new Map<string, string>()
-
-function cachedSlugify(text: string): string {
-  if (slugifyCache.has(text)) {
-    return slugifyCache.get(text)!
-  }
-  const result = slugify(text)
-  slugifyCache.set(text, result)
-  return result
-}
-
-function ProjectList({ projects }: { projects: Project[] }) {
-  return (
-    <div>
-      {projects.map(project => {
-        // Computed only once per unique project name
-        const slug = cachedSlugify(project.name)
-        
-        return <ProjectCard key={project.id} slug={slug} />
-      })}
-    </div>
-  )
-}
-```
-
-**Simpler pattern for single-value functions:**
-
-```typescript
-let isLoggedInCache: boolean | null = null
-
-function isLoggedIn(): boolean {
-  if (isLoggedInCache !== null) {
-    return isLoggedInCache
-  }
-  
-  isLoggedInCache = document.cookie.includes('auth=')
-  return isLoggedInCache
-}
-
-// Clear cache when auth changes
-function onAuthChange() {
-  isLoggedInCache = null
-}
-```
-
-Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.
-
-Reference: [https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast](https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast)
-
-### 7.5 Cache Storage API Calls
-
-**Impact: LOW-MEDIUM (reduces expensive I/O)**
-
-`localStorage`, `sessionStorage`, and `document.cookie` are synchronous and expensive. Cache reads in memory.
-
-**Incorrect: reads storage on every call**
-
-```typescript
-function getTheme() {
-  return localStorage.getItem('theme') ?? 'light'
-}
-// Called 10 times = 10 storage reads
-```
-
-**Correct: Map cache**
-
-```typescript
-const storageCache = new Map<string, string | null>()
-
-function getLocalStorage(key: string) {
-  if (!storageCache.has(key)) {
-    storageCache.set(key, localStorage.getItem(key))
-  }
-  return storageCache.get(key)
-}
-
-function setLocalStorage(key: string, value: string) {
-  localStorage.setItem(key, value)
-  storageCache.set(key, value)  // keep cache in sync
-}
-```
-
-Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.
-
-**Cookie caching:**
-
-```typescript
-let cookieCache: Record<string, string> | null = null
-
-function getCookie(name: string) {
-  if (!cookieCache) {
-    cookieCache = Object.fromEntries(
-      document.cookie.split('; ').map(c => c.split('='))
-    )
-  }
-  return cookieCache[name]
-}
-```
-
-**Important: invalidate on external changes**
-
-```typescript
-window.addEventListener('storage', (e) => {
-  if (e.key) storageCache.delete(e.key)
-})
-
-document.addEventListener('visibilitychange', () => {
-  if (document.visibilityState === 'visible') {
-    storageCache.clear()
-  }
-})
-```
-
-If storage can change externally (another tab, server-set cookies), invalidate cache:
-
-### 7.6 Combine Multiple Array Iterations
-
-**Impact: LOW-MEDIUM (reduces iterations)**
-
-Multiple `.filter()` or `.map()` calls iterate the array multiple times. Combine into one loop.
-
-**Incorrect: 3 iterations**
-
-```typescript
-const admins = users.filter(u => u.isAdmin)
-const testers = users.filter(u => u.isTester)
-const inactive = users.filter(u => !u.isActive)
-```
-
-**Correct: 1 iteration**
-
-```typescript
-const admins: User[] = []
-const testers: User[] = []
-const inactive: User[] = []
-
-for (const user of users) {
-  if (user.isAdmin) admins.push(user)
-  if (user.isTester) testers.push(user)
-  if (!user.isActive) inactive.push(user)
-}
-```
-
-### 7.7 Defer Non-Critical Work with requestIdleCallback
-
-**Impact: MEDIUM (keeps UI responsive during background tasks)**
-
-Use `requestIdleCallback()` to schedule non-critical work during browser idle periods. This keeps the main thread free for user interactions and animations, reducing jank and improving perceived performance.
-
-**Incorrect: blocks main thread during user interaction**
-
-```typescript
-function handleSearch(query: string) {
-  const results = searchItems(query)
-  setResults(results)
-
-  // These block the main thread immediately
-  analytics.track('search', { query })
-  saveToRecentSearches(query)
-  prefetchTopResults(results.slice(0, 3))
-}
-```
-
-**Correct: defers non-critical work to idle time**
-
-```typescript
-function handleSearch(query: string) {
-  const results = searchItems(query)
-  setResults(results)
-
-  // Defer non-critical work to idle periods
-  requestIdleCallback(() => {
-    analytics.track('search', { query })
-  })
-
-  requestIdleCallback(() => {
-    saveToRecentSearches(query)
-  })
-
-  requestIdleCallback(() => {
-    prefetchTopResults(results.slice(0, 3))
-  })
-}
-```
-
-**With timeout for required work:**
-
-```typescript
-// Ensure analytics fires within 2 seconds even if browser stays busy
-requestIdleCallback(
-  () => analytics.track('page_view', { path: location.pathname }),
-  { timeout: 2000 }
-)
-```
-
-**Chunking large tasks:**
-
-```typescript
-function processLargeDataset(items: Item[]) {
-  let index = 0
-
-  function processChunk(deadline: IdleDeadline) {
-    // Process items while we have idle time (aim for <50ms chunks)
-    while (index < items.length && deadline.timeRemaining() > 0) {
-      processItem(items[index])
-      index++
-    }
-
-    // Schedule next chunk if more items remain
-    if (index < items.length) {
-      requestIdleCallback(processChunk)
-    }
-  }
-
-  requestIdleCallback(processChunk)
-}
-```
-
-**With fallback for unsupported browsers:**
-
-```typescript
-const scheduleIdleWork = window.requestIdleCallback ?? ((cb: () => void) => setTimeout(cb, 1))
-
-scheduleIdleWork(() => {
-  // Non-critical work
-})
-```
-
-**When to use:**
-
-- Analytics and telemetry
-
-- Saving state to localStorage/IndexedDB
-
-- Prefetching resources for likely next actions
-
-- Processing non-urgent data transformations
-
-- Lazy initialization of non-critical features
-
-**When NOT to use:**
-
-- User-initiated actions that need immediate feedback
-
-- Rendering updates the user is waiting for
-
-- Time-sensitive operations
-
-### 7.8 Early Length Check for Array Comparisons
-
-**Impact: MEDIUM-HIGH (avoids expensive operations when lengths differ)**
-
-When comparing arrays with expensive operations (sorting, deep equality, serialization), check lengths first. If lengths differ, the arrays cannot be equal.
-
-In real-world applications, this optimization is especially valuable when the comparison runs in hot paths (event handlers, render loops).
-
-**Incorrect: always runs expensive comparison**
-
-```typescript
-function hasChanges(current: string[], original: string[]) {
-  // Always sorts and joins, even when lengths differ
-  return current.sort().join() !== original.sort().join()
-}
-```
-
-Two O(n log n) sorts run even when `current.length` is 5 and `original.length` is 100. There is also overhead of joining the arrays and comparing the strings.
-
-**Correct (O(1) length check first):**
-
-```typescript
-function hasChanges(current: string[], original: string[]) {
-  // Early return if lengths differ
-  if (current.length !== original.length) {
-    return true
-  }
-  // Only sort when lengths match
-  const currentSorted = current.toSorted()
-  const originalSorted = original.toSorted()
-  for (let i = 0; i < currentSorted.length; i++) {
-    if (currentSorted[i] !== originalSorted[i]) {
-      return true
-    }
-  }
-  return false
-}
-```
-
-This new approach is more efficient because:
-
-- It avoids the overhead of sorting and joining the arrays when lengths differ
-
-- It avoids consuming memory for the joined strings (especially important for large arrays)
-
-- It avoids mutating the original arrays
-
-- It returns early when a difference is found
-
-### 7.9 Early Return from Functions
-
-**Impact: LOW-MEDIUM (avoids unnecessary computation)**
-
-Return early when result is determined to skip unnecessary processing.
-
-**Incorrect: processes all items even after finding answer**
-
-```typescript
-function validateUsers(users: User[]) {
-  let hasError = false
-  let errorMessage = ''
-  
-  for (const user of users) {
-    if (!user.email) {
-      hasError = true
-      errorMessage = 'Email required'
-    }
-    if (!user.name) {
-      hasError = true
-      errorMessage = 'Name required'
-    }
-    // Continues checking all users even after error found
-  }
-  
-  return hasError ? { valid: false, error: errorMessage } : { valid: true }
-}
-```
-
-**Correct: returns immediately on first error**
-
-```typescript
-function validateUsers(users: User[]) {
-  for (const user of users) {
-    if (!user.email) {
-      return { valid: false, error: 'Email required' }
-    }
-    if (!user.name) {
-      return { valid: false, error: 'Name required' }
-    }
-  }
-
-  return { valid: true }
-}
-```
-
-### 7.10 Hoist RegExp Creation
-
-**Impact: LOW-MEDIUM (avoids recreation)**
-
-Don't create RegExp inside render. Hoist to module scope or memoize with `useMemo()`.
-
-**Incorrect: new RegExp every render**
-
-```tsx
-function Highlighter({ text, query }: Props) {
-  const regex = new RegExp(`(${query})`, 'gi')
-  const parts = text.split(regex)
-  return <>{parts.map((part, i) => ...)}</>
-}
-```
-
-**Correct: memoize or hoist**
-
-```tsx
-const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
-
-function Highlighter({ text, query }: Props) {
-  const regex = useMemo(
-    () => new RegExp(`(${escapeRegex(query)})`, 'gi'),
-    [query]
-  )
-  const parts = text.split(regex)
-  return <>{parts.map((part, i) => ...)}</>
-}
-```
-
-**Warning: global regex has mutable state**
-
-```typescript
-const regex = /foo/g
-regex.test('foo')  // true, lastIndex = 3
-regex.test('foo')  // false, lastIndex = 0
-```
-
-Global regex (`/g`) has mutable `lastIndex` state:
-
-### 7.11 Use flatMap to Map and Filter in One Pass
-
-**Impact: LOW-MEDIUM (eliminates intermediate array)**
-
-Chaining `.map().filter(Boolean)` creates an intermediate array and iterates twice. Use `.flatMap()` to transform and filter in a single pass.
-
-**Incorrect: 2 iterations, intermediate array**
-
-```typescript
-const userNames = users
-  .map(user => user.isActive ? user.name : null)
-  .filter(Boolean)
-```
-
-**Correct: 1 iteration, no intermediate array**
-
-```typescript
-const userNames = users.flatMap(user =>
-  user.isActive ? [user.name] : []
-)
-```
-
-**More examples:**
-
-```typescript
-// Extract valid emails from responses
-// Before
-const emails = responses
-  .map(r => r.success ? r.data.email : null)
-  .filter(Boolean)
-
-// After
-const emails = responses.flatMap(r =>
-  r.success ? [r.data.email] : []
-)
-
-// Parse and filter valid numbers
-// Before
-const numbers = strings
-  .map(s => parseInt(s, 10))
-  .filter(n => !isNaN(n))
-
-// After
-const numbers = strings.flatMap(s => {
-  const n = parseInt(s, 10)
-  return isNaN(n) ? [] : [n]
-})
-```
-
-**When to use:**
-
-- Transforming items while filtering some out
-
-- Conditional mapping where some inputs produce no output
-
-- Parsing/validating where invalid inputs should be skipped
-
-### 7.12 Use Loop for Min/Max Instead of Sort
-
-**Impact: LOW (O(n) instead of O(n log n))**
-
-Finding the smallest or largest element only requires a single pass through the array. Sorting is wasteful and slower.
-
-**Incorrect (O(n log n) - sort to find latest):**
-
-```typescript
-interface Project {
-  id: string
-  name: string
-  updatedAt: number
-}
-
-function getLatestProject(projects: Project[]) {
-  const sorted = [...projects].sort((a, b) => b.updatedAt - a.updatedAt)
-  return sorted[0]
-}
-```
-
-Sorts the entire array just to find the maximum value.
-
-**Incorrect (O(n log n) - sort for oldest and newest):**
-
-```typescript
-function getOldestAndNewest(projects: Project[]) {
-  const sorted = [...projects].sort((a, b) => a.updatedAt - b.updatedAt)
-  return { oldest: sorted[0], newest: sorted[sorted.length - 1] }
-}
-```
-
-Still sorts unnecessarily when only min/max are needed.
-
-**Correct (O(n) - single loop):**
-
-```typescript
-function getLatestProject(projects: Project[]) {
-  if (projects.length === 0) return null
-  
-  let latest = projects[0]
-  
-  for (let i = 1; i < projects.length; i++) {
-    if (projects[i].updatedAt > latest.updatedAt) {
-      latest = projects[i]
-    }
-  }
-  
-  return latest
-}
-
-function getOldestAndNewest(projects: Project[]) {
-  if (projects.length === 0) return { oldest: null, newest: null }
-  
-  let oldest = projects[0]
-  let newest = projects[0]
-  
-  for (let i = 1; i < projects.length; i++) {
-    if (projects[i].updatedAt < oldest.updatedAt) oldest = projects[i]
-    if (projects[i].updatedAt > newest.updatedAt) newest = projects[i]
-  }
-  
-  return { oldest, newest }
-}
-```
-
-Single pass through the array, no copying, no sorting.
-
-**Alternative: Math.min/Math.max for small arrays**
-
-```typescript
-const numbers = [5, 2, 8, 1, 9]
-const min = Math.min(...numbers)
-const max = Math.max(...numbers)
-```
-
-This works for small arrays, but can be slower or just throw an error for very large arrays due to spread operator limitations. Maximal array length is approximately 124000 in Chrome 143 and 638000 in Safari 18; exact numbers may vary - see [the fiddle](https://jsfiddle.net/qw1jabsx/4/). Use the loop approach for reliability.
-
-### 7.13 Use Set/Map for O(1) Lookups
-
-**Impact: LOW-MEDIUM (O(n) to O(1))**
-
-Convert arrays to Set/Map for repeated membership checks.
-
-**Incorrect (O(n) per check):**
-
-```typescript
-const allowedIds = ['a', 'b', 'c', ...]
-items.filter(item => allowedIds.includes(item.id))
-```
-
-**Correct (O(1) per check):**
-
-```typescript
-const allowedIds = new Set(['a', 'b', 'c', ...])
-items.filter(item => allowedIds.has(item.id))
-```
-
-### 7.14 Use toSorted() Instead of sort() for Immutability
-
-**Impact: MEDIUM-HIGH (prevents mutation bugs in React state)**
-
-`.sort()` mutates the array in place, which can cause bugs with React state and props. Use `.toSorted()` to create a new sorted array without mutation.
-
-**Incorrect: mutates original array**
-
-```typescript
-function UserList({ users }: { users: User[] }) {
-  // Mutates the users prop array!
-  const sorted = useMemo(
-    () => users.sort((a, b) => a.name.localeCompare(b.name)),
-    [users]
-  )
-  return <div>{sorted.map(renderUser)}</div>
-}
-```
-
-**Correct: creates new array**
-
-```typescript
-function UserList({ users }: { users: User[] }) {
-  // Creates new sorted array, original unchanged
-  const sorted = useMemo(
-    () => users.toSorted((a, b) => a.name.localeCompare(b.name)),
-    [users]
-  )
-  return <div>{sorted.map(renderUser)}</div>
-}
-```
-
-**Why this matters in React:**
-
-1. Props/state mutations break React's immutability model - React expects props and state to be treated as read-only
-
-2. Causes stale closure bugs - Mutating arrays inside closures (callbacks, effects) can lead to unexpected behavior
-
-**Browser support: fallback for older browsers**
-
-```typescript
-// Fallback for older browsers
-const sorted = [...items].sort((a, b) => a.value - b.value)
-```
-
-`.toSorted()` is available in all modern browsers (Chrome 110+, Safari 16+, Firefox 115+, Node.js 20+). For older environments, use spread operator:
-
-**Other immutable array methods:**
-
-- `.toSorted()` - immutable sort
-
-- `.toReversed()` - immutable reverse
-
-- `.toSpliced()` - immutable splice
-
-- `.with()` - immutable element replacement
-
----
-
-## 8. Advanced Patterns
-
-**Impact: LOW**
-
-Advanced patterns for specific cases that require careful implementation.
-
-### 8.1 Initialize App Once, Not Per Mount
-
-**Impact: LOW-MEDIUM (avoids duplicate init in development)**
-
-Do not put app-wide initialization that must run once per app load inside `useEffect([])` of a component. Components can remount and effects will re-run. Use a module-level guard or top-level init in the entry module instead.
-
-**Incorrect: runs twice in dev, re-runs on remount**
-
-```tsx
-function Comp() {
-  useEffect(() => {
-    loadFromStorage()
-    checkAuthToken()
-  }, [])
-
-  // ...
-}
-```
-
-**Correct: once per app load**
-
-```tsx
-let didInit = false
-
-function Comp() {
-  useEffect(() => {
-    if (didInit) return
-    didInit = true
-    loadFromStorage()
-    checkAuthToken()
-  }, [])
-
-  // ...
-}
-```
-
-Reference: [https://react.dev/learn/you-might-not-need-an-effect#initializing-the-application](https://react.dev/learn/you-might-not-need-an-effect#initializing-the-application)
-
-### 8.2 Store Event Handlers in Refs
-
-**Impact: LOW (stable subscriptions)**
-
-Store callbacks in refs when used in effects that shouldn't re-subscribe on callback changes.
-
-**Incorrect: re-subscribes on every render**
-
-```tsx
-function useWindowEvent(event: string, handler: (e) => void) {
-  useEffect(() => {
-    window.addEventListener(event, handler)
-    return () => window.removeEventListener(event, handler)
-  }, [event, handler])
-}
-```
-
-**Correct: stable subscription**
-
-```tsx
-import { useEffectEvent } from 'react'
-
-function useWindowEvent(event: string, handler: (e) => void) {
-  const onEvent = useEffectEvent(handler)
-
-  useEffect(() => {
-    window.addEventListener(event, onEvent)
-    return () => window.removeEventListener(event, onEvent)
-  }, [event])
-}
-```
-
-**Alternative: use `useEffectEvent` if you're on latest React:**
-
-`useEffectEvent` provides a cleaner API for the same pattern: it creates a stable function reference that always calls the latest version of the handler.
-
-### 8.3 useEffectEvent for Stable Callback Refs
-
-**Impact: LOW (prevents effect re-runs)**
-
-Access latest values in callbacks without adding them to dependency arrays. Prevents effect re-runs while avoiding stale closures.
-
-**Incorrect: effect re-runs on every callback change**
-
-```tsx
-function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
-  const [query, setQuery] = useState('')
-
-  useEffect(() => {
-    const timeout = setTimeout(() => onSearch(query), 300)
-    return () => clearTimeout(timeout)
-  }, [query, onSearch])
-}
-```
-
-**Correct: using React's useEffectEvent**
-
-```tsx
-import { useEffectEvent } from 'react';
-
-function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
-  const [query, setQuery] = useState('')
-  const onSearchEvent = useEffectEvent(onSearch)
-
-  useEffect(() => {
-    const timeout = setTimeout(() => onSearchEvent(query), 300)
-    return () => clearTimeout(timeout)
-  }, [query])
-}
-```
-
----
-
-## References
-
-1. [https://react.dev](https://react.dev)
-2. [https://nextjs.org](https://nextjs.org)
-3. [https://swr.vercel.app](https://swr.vercel.app)
-4. [https://github.com/shuding/better-all](https://github.com/shuding/better-all)
-5. [https://github.com/isaacs/node-lru-cache](https://github.com/isaacs/node-lru-cache)
-6. [https://vercel.com/blog/how-we-optimized-package-imports-in-next-js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)
-7. [https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast](https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast)

+ 0 - 123
neo/.agents/skills/vercel-react-best-practices/README.md

@@ -1,123 +0,0 @@
-# React Best Practices
-
-A structured repository for creating and maintaining React Best Practices optimized for agents and LLMs.
-
-## Structure
-
-- `rules/` - Individual rule files (one per rule)
-  - `_sections.md` - Section metadata (titles, impacts, descriptions)
-  - `_template.md` - Template for creating new rules
-  - `area-description.md` - Individual rule files
-- `src/` - Build scripts and utilities
-- `metadata.json` - Document metadata (version, organization, abstract)
-- __`AGENTS.md`__ - Compiled output (generated)
-- __`test-cases.json`__ - Test cases for LLM evaluation (generated)
-
-## Getting Started
-
-1. Install dependencies:
-   ```bash
-   pnpm install
-   ```
-
-2. Build AGENTS.md from rules:
-   ```bash
-   pnpm build
-   ```
-
-3. Validate rule files:
-   ```bash
-   pnpm validate
-   ```
-
-4. Extract test cases:
-   ```bash
-   pnpm extract-tests
-   ```
-
-## Creating a New Rule
-
-1. Copy `rules/_template.md` to `rules/area-description.md`
-2. Choose the appropriate area prefix:
-   - `async-` for Eliminating Waterfalls (Section 1)
-   - `bundle-` for Bundle Size Optimization (Section 2)
-   - `server-` for Server-Side Performance (Section 3)
-   - `client-` for Client-Side Data Fetching (Section 4)
-   - `rerender-` for Re-render Optimization (Section 5)
-   - `rendering-` for Rendering Performance (Section 6)
-   - `js-` for JavaScript Performance (Section 7)
-   - `advanced-` for Advanced Patterns (Section 8)
-3. Fill in the frontmatter and content
-4. Ensure you have clear examples with explanations
-5. Run `pnpm build` to regenerate AGENTS.md and test-cases.json
-
-## Rule File Structure
-
-Each rule file should follow this structure:
-
-```markdown
----
-title: Rule Title Here
-impact: MEDIUM
-impactDescription: Optional description
-tags: tag1, tag2, tag3
----
-
-## Rule Title Here
-
-Brief explanation of the rule and why it matters.
-
-**Incorrect (description of what's wrong):**
-
-```typescript
-// Bad code example
-```
-
-**Correct (description of what's right):**
-
-```typescript
-// Good code example
-```
-
-Optional explanatory text after examples.
-
-Reference: [Link](https://example.com)
-
-## File Naming Convention
-
-- Files starting with `_` are special (excluded from build)
-- Rule files: `area-description.md` (e.g., `async-parallel.md`)
-- Section is automatically inferred from filename prefix
-- Rules are sorted alphabetically by title within each section
-- IDs (e.g., 1.1, 1.2) are auto-generated during build
-
-## Impact Levels
-
-- `CRITICAL` - Highest priority, major performance gains
-- `HIGH` - Significant performance improvements
-- `MEDIUM-HIGH` - Moderate-high gains
-- `MEDIUM` - Moderate performance improvements
-- `LOW-MEDIUM` - Low-medium gains
-- `LOW` - Incremental improvements
-
-## Scripts
-
-- `pnpm build` - Compile rules into AGENTS.md
-- `pnpm validate` - Validate all rule files
-- `pnpm extract-tests` - Extract test cases for LLM evaluation
-- `pnpm dev` - Build and validate
-
-## Contributing
-
-When adding or modifying rules:
-
-1. Use the correct filename prefix for your section
-2. Follow the `_template.md` structure
-3. Include clear bad/good examples with explanations
-4. Add appropriate tags
-5. Run `pnpm build` to regenerate AGENTS.md and test-cases.json
-6. Rules are automatically sorted by title - no need to manage numbers!
-
-## Acknowledgments
-
-Originally created by [@shuding](https://x.com/shuding) at [Vercel](https://vercel.com).

+ 0 - 145
neo/.agents/skills/vercel-react-best-practices/SKILL.md

@@ -1,145 +0,0 @@
----
-name: vercel-react-best-practices
-description: React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
-license: MIT
-metadata:
-  author: vercel
-  version: "1.0.0"
----
-
-# Vercel React Best Practices
-
-Comprehensive performance optimization guide for React and Next.js applications, maintained by Vercel. Contains 65 rules across 8 categories, prioritized by impact to guide automated refactoring and code generation.
-
-## When to Apply
-
-Reference these guidelines when:
-- Writing new React components or Next.js pages
-- Implementing data fetching (client or server-side)
-- Reviewing code for performance issues
-- Refactoring existing React/Next.js code
-- Optimizing bundle size or load times
-
-## Rule Categories by Priority
-
-| Priority | Category | Impact | Prefix |
-|----------|----------|--------|--------|
-| 1 | Eliminating Waterfalls | CRITICAL | `async-` |
-| 2 | Bundle Size Optimization | CRITICAL | `bundle-` |
-| 3 | Server-Side Performance | HIGH | `server-` |
-| 4 | Client-Side Data Fetching | MEDIUM-HIGH | `client-` |
-| 5 | Re-render Optimization | MEDIUM | `rerender-` |
-| 6 | Rendering Performance | MEDIUM | `rendering-` |
-| 7 | JavaScript Performance | LOW-MEDIUM | `js-` |
-| 8 | Advanced Patterns | LOW | `advanced-` |
-
-## Quick Reference
-
-### 1. Eliminating Waterfalls (CRITICAL)
-
-- `async-defer-await` - Move await into branches where actually used
-- `async-parallel` - Use Promise.all() for independent operations
-- `async-dependencies` - Use better-all for partial dependencies
-- `async-api-routes` - Start promises early, await late in API routes
-- `async-suspense-boundaries` - Use Suspense to stream content
-
-### 2. Bundle Size Optimization (CRITICAL)
-
-- `bundle-barrel-imports` - Import directly, avoid barrel files
-- `bundle-dynamic-imports` - Use next/dynamic for heavy components
-- `bundle-defer-third-party` - Load analytics/logging after hydration
-- `bundle-conditional` - Load modules only when feature is activated
-- `bundle-preload` - Preload on hover/focus for perceived speed
-
-### 3. Server-Side Performance (HIGH)
-
-- `server-auth-actions` - Authenticate server actions like API routes
-- `server-cache-react` - Use React.cache() for per-request deduplication
-- `server-cache-lru` - Use LRU cache for cross-request caching
-- `server-dedup-props` - Avoid duplicate serialization in RSC props
-- `server-hoist-static-io` - Hoist static I/O (fonts, logos) to module level
-- `server-serialization` - Minimize data passed to client components
-- `server-parallel-fetching` - Restructure components to parallelize fetches
-- `server-parallel-nested-fetching` - Chain nested fetches per item in Promise.all
-- `server-after-nonblocking` - Use after() for non-blocking operations
-
-### 4. Client-Side Data Fetching (MEDIUM-HIGH)
-
-- `client-swr-dedup` - Use SWR for automatic request deduplication
-- `client-event-listeners` - Deduplicate global event listeners
-- `client-passive-event-listeners` - Use passive listeners for scroll
-- `client-localstorage-schema` - Version and minimize localStorage data
-
-### 5. Re-render Optimization (MEDIUM)
-
-- `rerender-defer-reads` - Don't subscribe to state only used in callbacks
-- `rerender-memo` - Extract expensive work into memoized components
-- `rerender-memo-with-default-value` - Hoist default non-primitive props
-- `rerender-dependencies` - Use primitive dependencies in effects
-- `rerender-derived-state` - Subscribe to derived booleans, not raw values
-- `rerender-derived-state-no-effect` - Derive state during render, not effects
-- `rerender-functional-setstate` - Use functional setState for stable callbacks
-- `rerender-lazy-state-init` - Pass function to useState for expensive values
-- `rerender-simple-expression-in-memo` - Avoid memo for simple primitives
-- `rerender-split-combined-hooks` - Split hooks with independent dependencies
-- `rerender-move-effect-to-event` - Put interaction logic in event handlers
-- `rerender-transitions` - Use startTransition for non-urgent updates
-- `rerender-use-deferred-value` - Defer expensive renders to keep input responsive
-- `rerender-use-ref-transient-values` - Use refs for transient frequent values
-- `rerender-no-inline-components` - Don't define components inside components
-
-### 6. Rendering Performance (MEDIUM)
-
-- `rendering-animate-svg-wrapper` - Animate div wrapper, not SVG element
-- `rendering-content-visibility` - Use content-visibility for long lists
-- `rendering-hoist-jsx` - Extract static JSX outside components
-- `rendering-svg-precision` - Reduce SVG coordinate precision
-- `rendering-hydration-no-flicker` - Use inline script for client-only data
-- `rendering-hydration-suppress-warning` - Suppress expected mismatches
-- `rendering-activity` - Use Activity component for show/hide
-- `rendering-conditional-render` - Use ternary, not && for conditionals
-- `rendering-usetransition-loading` - Prefer useTransition for loading state
-- `rendering-resource-hints` - Use React DOM resource hints for preloading
-- `rendering-script-defer-async` - Use defer or async on script tags
-
-### 7. JavaScript Performance (LOW-MEDIUM)
-
-- `js-batch-dom-css` - Group CSS changes via classes or cssText
-- `js-index-maps` - Build Map for repeated lookups
-- `js-cache-property-access` - Cache object properties in loops
-- `js-cache-function-results` - Cache function results in module-level Map
-- `js-cache-storage` - Cache localStorage/sessionStorage reads
-- `js-combine-iterations` - Combine multiple filter/map into one loop
-- `js-length-check-first` - Check array length before expensive comparison
-- `js-early-exit` - Return early from functions
-- `js-hoist-regexp` - Hoist RegExp creation outside loops
-- `js-min-max-loop` - Use loop for min/max instead of sort
-- `js-set-map-lookups` - Use Set/Map for O(1) lookups
-- `js-tosorted-immutable` - Use toSorted() for immutability
-- `js-flatmap-filter` - Use flatMap to map and filter in one pass
-- `js-request-idle-callback` - Defer non-critical work to browser idle time
-
-### 8. Advanced Patterns (LOW)
-
-- `advanced-event-handler-refs` - Store event handlers in refs
-- `advanced-init-once` - Initialize app once per app load
-- `advanced-use-latest` - useLatest for stable callback refs
-
-## How to Use
-
-Read individual rule files for detailed explanations and code examples:
-
-```
-rules/async-parallel.md
-rules/bundle-barrel-imports.md
-```
-
-Each rule file contains:
-- Brief explanation of why it matters
-- Incorrect code example with explanation
-- Correct code example with explanation
-- Additional context and references
-
-## Full Compiled Document
-
-For the complete guide with all rules expanded: `AGENTS.md`

+ 0 - 46
neo/.agents/skills/vercel-react-best-practices/rules/_sections.md

@@ -1,46 +0,0 @@
-# Sections
-
-This file defines all sections, their ordering, impact levels, and descriptions.
-The section ID (in parentheses) is the filename prefix used to group rules.
-
----
-
-## 1. Eliminating Waterfalls (async)
-
-**Impact:** CRITICAL  
-**Description:** Waterfalls are the #1 performance killer. Each sequential await adds full network latency. Eliminating them yields the largest gains.
-
-## 2. Bundle Size Optimization (bundle)
-
-**Impact:** CRITICAL  
-**Description:** Reducing initial bundle size improves Time to Interactive and Largest Contentful Paint.
-
-## 3. Server-Side Performance (server)
-
-**Impact:** HIGH  
-**Description:** Optimizing server-side rendering and data fetching eliminates server-side waterfalls and reduces response times.
-
-## 4. Client-Side Data Fetching (client)
-
-**Impact:** MEDIUM-HIGH  
-**Description:** Automatic deduplication and efficient data fetching patterns reduce redundant network requests.
-
-## 5. Re-render Optimization (rerender)
-
-**Impact:** MEDIUM  
-**Description:** Reducing unnecessary re-renders minimizes wasted computation and improves UI responsiveness.
-
-## 6. Rendering Performance (rendering)
-
-**Impact:** MEDIUM  
-**Description:** Optimizing the rendering process reduces the work the browser needs to do.
-
-## 7. JavaScript Performance (js)
-
-**Impact:** LOW-MEDIUM  
-**Description:** Micro-optimizations for hot paths can add up to meaningful improvements.
-
-## 8. Advanced Patterns (advanced)
-
-**Impact:** LOW  
-**Description:** Advanced patterns for specific cases that require careful implementation.

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio