{"owner":"getpaseo","repo":"paseo","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["CLAUDE.md"],"skills":{"CLAUDE.md":"# CLAUDE.md\n\nPaseo is a mobile app for monitoring and controlling your local AI coding agents from anywhere. Your dev environment, in your pocket. Connects directly to your actual development environment — your code stays on your machine.\n\n**Supported agents:** Claude Code, Codex, GitHub Copilot, OpenCode, and Pi.\n\n## Repository map\n\nThis is an npm workspace monorepo:\n\n- `packages/server` — Daemon: agent lifecycle, WebSocket API, MCP server\n- `packages/app` — Mobile + web client (Expo)\n- `packages/cli` — Docker-style CLI (`paseo run/ls/logs/wait`)\n- `packages/relay` — E2E encrypted relay for remote access\n- `packages/desktop` — Electron desktop wrapper\n- `packages/website` — Marketing site (paseo.sh)\n\n## Docs\n\n`docs/` is the source of truth for system-level and process-level knowledge. **\"The docs\", \"check the docs\", or \"check the X docs\" always mean this directory — not the web.** Look here before fetching anything online; the docs capture gotchas and conventions you cannot derive from the code or external sources.\n\nAt the start of non-trivial work, list `docs/` and skim anything relevant to the task.\n\n| Doc                                                                | What's in it                                                                                                                   |\n| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |\n| [docs/product.md](docs/product.md)                                 | What Paseo is, who it's for, where it's going                                                                                  |\n| [docs/architecture.md](docs/architecture.md)                       | System design, package layering, WebSocket protocol, agent lifecycle, data flow                                                |\n| [docs/agent-lifecycle.md](docs/agent-lifecycle.md)                 | Agent states, parent/child relationships, archive semantics, tabs vs archive, subagents track                                  |\n| [docs/data-model.md](docs/data-model.md)                           | File-based JSON persistence, Zod schemas, atomic writes, no migrations                                                         |\n| [docs/glossary.md](docs/glossary.md)                               | Authoritative terminology — UI label wins, no synonyms                                                                         |\n| [docs/coding-standards.md](docs/coding-standards.md)               | Type hygiene, error handling, state design, React patterns, file organization                                                  |\n| [docs/design.md](docs/design.md)                                   | Design system — tokens, buttons, hierarchy, density, alignment rails, states, what's forbidden                                 |\n| [docs/forms.md](docs/forms.md)                                     | Form architecture — non-React form model, form kit, load-state gating; the schedule form is the golden example                 |\n| [docs/hover.md](docs/hover.md)                                     | Hover — the canonical pattern (plain View + onPointerEnter/Leave, separate inner Pressable) and the three ways agents break it |\n| [docs/unistyles.md](docs/unistyles.md)                             | Unistyles gotchas — `useUnistyles()` is forbidden, alternatives in order                                                       |\n| [docs/floating-panels.md](docs/floating-panels.md)                 | Anchored popovers — Portal/Modal escape for Android, lifecycle gates, keyboard-shared-value, status-bar offset, the flash      |\n| [docs/menus.md](docs/menus.md)                                     | The menu engine — popover vs sheet, submenu pages, hover intent, when a decision earns a submenu                               |\n| [docs/expo-router.md](docs/expo-router.md)                         | Expo Router route ownership, startup restore, and native blank-screen gotchas                                                  |\n| [docs/file-icons.md](docs/file-icons.md)                           | Material icon theme integration for the file explorer                                                                          |\n| [docs/providers.md](docs/providers.md)                             | Adding a new agent provider end-to-end                                                                                         |\n| [docs/forge-providers.md](docs/forge-providers.md)                 | Adding a git forge: registry/manifest, drop-in checklist, self-host/GHES, the two facts tiers                                  |\n| [docs/custom-providers.md](docs/custom-providers.md)               | Custom provider config: Z.AI, Alibaba/Qwen, ACP agents, profiles, custom binaries                                              |\n| [docs/plugins.md](docs/plugins.md)                                 | Local plugin manifest, directory source config, RPCs, native surfaces, and attachment sources                                  |\n| [docs/service-proxy.md](docs/service-proxy.md)                     | Service proxy: exposing workspace scripts at public URLs, DNS setup, reverse proxy config                                      |\n| [docs/development.md](docs/development.md)                         | Dev server, build sync gotchas, CLI reference, agent state, Playwright MCP                                                     |\n| [docs/rpc-namespacing.md](docs/rpc-namespacing.md)                 | WebSocket RPC naming convention — dotted namespaces and `.request`/`.response` pairs                                           |\n| [docs/protocol-compatibility.md](docs/protocol-compatibility.md)   | Why app/daemon versions drift, protocol vs feature contract, capability gating, COMPAT tagging                                 |\n| [docs/protocol-validation.md](docs/protocol-validation.md)         | zod-aot generated inbound WebSocket validation, patched compiler regressions, schema-purity rules                              |\n| [docs/terminal-performance.md](docs/terminal-performance.md)       | Terminal latency pipeline, coalescing/backpressure invariants, benchmark + perf spec usage                                     |\n| [docs/file-observation.md](docs/file-observation.md)               | Recursive watcher ownership, Linux constraints, teardown invariants, and Parcel comparison                                     |\n| [docs/testing.md](docs/testing.md)                                 | TDD workflow, determinism, real dependencies over mocks, test organization                                                     |\n| [docs/qa.md](docs/qa.md)                                           | QA evidence bar for pull requests — platform matrix, version drift, performance, UI proof                                      |\n| [docs/mobile-testing.md](docs/mobile-testing.md)                   | Maestro and mobile test workflows                                                                                              |\n| [docs/mobile-panels.md](docs/mobile-panels.md)                     | Compact left/center/right panel ownership, worklet motion, gesture revisions, and Fabric constraints                           |\n| [docs/ad-hoc-daemon-testing.md](docs/ad-hoc-daemon-testing.md)     | Isolated in-process daemon test harness                                                                                        |\n| [docs/browser-capture-harness.md](docs/browser-capture-harness.md) | Real-Electron browser screenshot harness and compositor-surface gotcha                                                         |\n| [docs/android.md](docs/android.md)                                 | App variants, local/cloud builds, EAS workflows                                                                                |\n| [docs/docker.md](docs/docker.md)                                   | Running the daemon and bundled web UI in Docker, volumes, agent images, security                                               |\n| [docs/release.md](docs/release.md)                                 | Release playbook, draft releases, completion checklist                                                                         |\n| [docs/terminal-activity.md](docs/terminal-activity.md)             | Terminal activity indicators — source-agnostic tracker, agent hook reporting, adding a new hook provider                       |\n| [SECURITY.md](SECURITY.md)                                         | Relay threat model, E2E encryption, DNS rebinding, agent auth                                                                  |\n| [public-docs/hub/security.md](public-docs/hub/security.md)         | Public Hub guide — trust boundaries, untrusted triggers, provider controls, and output authority                               |\n\n### Writing docs\n\n- **Integrate, don't append.** Find the doc that owns the subject and rewrite the part that is now wrong. The standard failure is finishing a task and adding a paragraph to the bottom of the closest-looking doc; ten tasks later the doc is a pile of paragraphs in discovery order. `docs/custom-providers.md` is what that looks like.\n- **Don't document logic.** Prose that restates code drifts from the code and loses. Write down what the code can't tell you: why something is shaped the way it is, the gotcha that cost an afternoon, conventions nothing enforces, constraints that span packages or versions. If a reader could get it in two minutes by opening the file, cut it.\n- **One fact, one doc.** Every other mention is a link. If you are about to write the same paragraph in two docs, one of them is a link.\n- **Respect the layers.** `CONTRIBUTING.md` and this file name things and link out. Activity docs like `docs/qa.md` and `docs/testing.md` set the bar for a kind of work. Subject docs like `docs/unistyles.md` own one thing completely. A layer never re-explains the one below it.\n- **One subject per doc.** If the subject doesn't fit in a sentence, split the doc. A section per provider, vendor, or platform is a table plus one worked example.\n- **Delete.** Obsolete sections go. Prefer a `packages/app/src/thing.ts:120` reference over a pasted block.\n- **New doc?** Add a row to the table above and link it from the docs that should send readers there.\n- Code-level facts belong in comments next to the code, not here.\n\n### Doc voice\n\nPlain and short. Second person. State the rule, then the reason when the reason isn't obvious. Match the doc you're editing.\n\nDo not:\n\n- Write a sentence to land a point. \"It's not X, it's Y\", \"That's not a Z, that's a W\", and every other setup-and-punchline shape.\n- Add a clause that only asserts importance: \"and that matters\", \"which is what keeps it working\", \"this is critical\".\n- Use \"honest\", \"robust\", \"seamless\", \"powerful\", \"simply\", \"just\", \"delightful\".\n- Restate something you already said, in different words, for emphasis.\n- Hedge with \"generally\", \"typically\", or \"you may want to\" when the answer is \"do this\".\n- Clear your throat: \"It's worth noting that\", \"In order to\", \"This section covers\".\n\n## Quick start\n\n```bash\nnpm run dev                          # Start the dev daemon\nnpm run dev:app                      # Start Expo against the dev daemon\nnpm run dev:desktop                  # Start Electron desktop dev\nnpm run cli -- ls -a -g              # List all agents\nnpm run cli -- daemon status         # Check daemon status\nnpm run typecheck                    # Always run after changes\nnpm run lint                         # Always run after changes\nnpm run format                       # Auto-format with Biome\nnpm run format:check                 # Check formatting without writing\n```\n\nRepo dev commands use checkout-local state by default. In this checkout, `PASEO_HOME` resolves to `.dev/paseo-home`, and `npm run cli -- ...` targets that same dev home automatically. The packaged desktop app and production-style daemon keep using `~/.paseo` on port `6767`.\n\nSee [docs/development.md](docs/development.md) for full setup, build sync requirements, and debugging.\n\n## Critical rules\n\n- **NEVER restart the main Paseo daemon on port 6767 without permission** — it manages all running agents. If you're an agent, restarting it kills your own process.\n- **NEVER assume a timeout means the service needs restarting** — timeouts can be transient.\n- **NEVER add auth checks to tests** — agent providers handle their own auth.\n- **Before changing app routes, startup routing, remembered workspace restore, or active workspace selection, read [docs/expo-router.md](docs/expo-router.md).**\n- **NEVER run the full test suite locally.** The test suites are heavy and will freeze the machine, especially if multiple agents run them in parallel. Rules:\n  - Run only the specific test file you changed: `npx vitest run <file> --bail=1`\n  - Never run `npm run test` for an entire workspace unless explicitly asked.\n  - If you must run a broad suite, pipe output to a file and read it afterward: `npx vitest run <file> --bail=1 > /tmp/test-output.txt 2>&1` then read the file.\n  - Never re-run a test suite that another agent already ran and reported green — trust the result.\n  - For full suite verification, push to CI and check GitHub Actions instead.\n- **Always run typecheck and lint after every change.**\n- **Build workspace packages before diagnosing cross-package type errors.** This repo consumes generated declarations across workspaces. If typecheck fails in a package that depends on another workspace, rebuild the owning stack first so `dist` declarations are current:\n  - `npm run build:client` — rebuild protocol and client declarations.\n  - `npm run build:server` — rebuild highlight, relay, protocol, client, server, and CLI when server/CLI types may be stale.\n  - Do not patch inferred callback parameters or add local duplicate types just to silence stale declaration errors.\n- **Run `npm run format` before committing.** This repo uses Biome for formatting. Do not manually fix formatting — let the formatter handle it.\n- **Always use npm scripts for linting and formatting.** Do not run tools directly with `npx eslint`, `npx oxfmt`, `npx oxlint`, or package-local binaries. For targeted checks, pass file paths through the npm script:\n  - `npm run lint -- packages/app/src/components/message.tsx`\n  - `npm run format:files -- CLAUDE.md packages/app/src/components/message.tsx`\n- **The protocol stays backward-compatible. Features don't have to.** Read [docs/protocol-compatibility.md](docs/protocol-compatibility.md) before touching `packages/protocol`. The short version:\n  - **Protocol contract (always):** an old client parses messages from a new daemon, and a new daemon parses messages from an old client. New fields are optional; never narrow, never remove, never require. Wire schemas stay pure — no `.transform()`, `.catch()`, or `.preprocess()`.\n  - **Feature contract (per-feature):** gate the capability once on `server_info.features.*`, then run the feature or tell the user to update the host. No fallback paths, no defensive branches.\n  - **Every shim is tagged.** `// COMPAT(name): added in vX, remove after <date>` at the site that has to be deleted. `rg \"COMPAT\\(\"` is the cleanup backlog; untagged back-compat is permanent by accident.\n  - **New RPCs use dotted namespaces with direction suffixes.** Follow [docs/rpc-namespacing.md](docs/rpc-namespacing.md): `domain.provider.operation.request` pairs with `domain.provider.operation.response`. Existing flat RPC names will migrate over time; don't add new ones.\n\n## Platform gating\n\nThe app runs on iOS, Android, web (browser), and web (Electron desktop). Code is cross-platform by default. Gate only when you must. Import gates from `@/constants/platform`.\n\n### The four gates\n\n| Gate                       | Type      | When to use                                                                                                                 |\n| -------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------- |\n| `isWeb`                    | constant  | DOM APIs — `document`, `window`, `<div>`, `addEventListener`, `ResizeObserver`. This is the **exception**, not the default. |\n| `isNative`                 | constant  | Native-only APIs — Haptics, `StatusBar.currentHeight`, push tokens, camera/scanner, `expo-av`.                              |\n| `getIsElectron()`          | cached fn | Desktop wrapper features — file dialogs, titlebar drag region, daemon management, app updates, dock badges.                 |\n| `useIsCompactFormFactor()` | hook      | Layout decisions — sidebar overlay vs pinned, modal vs full screen, single-panel vs split. From `@/constants/layout`.       |\n\n### Decision matrix\n\n| I need to...                                                   | Use                                                                       |\n| -------------------------------------------------------------- | ------------------------------------------------------------------------- |\n| Access DOM (`document`, `window`, `<div>`, `addEventListener`) | `if (isWeb)`                                                              |\n| Use a native-only API (Haptics, push tokens, camera)           | `if (isNative)`                                                           |\n| Use an Electron bridge (file dialog, titlebar, updates)        | `if (getIsElectron())`                                                    |\n| Switch layout between phone and tablet/desktop                 | `useIsCompactFormFactor()`                                                |\n| Show something on hover, always-visible on native              | `isHovered \\|\\| isNative \\|\\| isCompact` (hover only works on web)        |\n| Gate to iOS or Android specifically                            | `Platform.OS === \"ios\"` / `Platform.OS === \"android\"` (rare, keep inline) |\n\n### Rules\n\n- **Default is cross-platform.** Don't gate unless you have a specific reason.\n- **Prefer Metro file extensions over `if` statements.** When a module has fundamentally different implementations per platform, use `.web.ts` / `.native.ts` file extensions instead of runtime `if (isWeb)` branches. Metro resolves the correct file at build time — the unused platform code is never bundled. Reserve `if (isWeb)` for small, inline checks (a single line or a few props). If you find yourself writing a large `if (isWeb) { ... } else { ... }` block, split into separate files instead.\n  ```\n  hooks/\n    use-audio-recorder.web.ts    ← uses Web Audio API\n    use-audio-recorder.native.ts ← uses expo-audio\n  ```\n  Import as `@/hooks/use-audio-recorder` — Metro picks the right file automatically.\n- **Use `.electron.ts` / `.electron.tsx` for Electron-only web modules.** Electron is still the Metro `web` platform, but desktop dev/build sets `PASEO_WEB_PLATFORM=electron`, so Metro first looks for `.electron.*` files and falls back to normal `.web.*` files. Use this when the implementation depends on Electron-only behavior such as `webviewTag`, desktop preload APIs, or the Electron bridge. Keep plain browser web in `.web.*`, and keep native fallbacks in the base file or `.native.*`.\n  ```\n  desktop/browser/pane/\n    index.electron.tsx ← Electron <webview> implementation\n    index.web.tsx      ← plain web fallback\n    index.tsx          ← native fallback\n  ```\n  Import as `@/desktop/browser/pane` — Electron desktop gets the `.electron.tsx` file, browser web gets `.web.tsx`, and native gets the native/base implementation.\n- **NEVER use raw DOM APIs without `isWeb` guard.** DOM APIs crash native. Casting a RN ref to `HTMLElement` is a red flag — ensure the block is web-only.\n- **NEVER use `onPointerEnter`/`onPointerLeave`.** They don't fire on native iOS.\n- **Hover only works on web.** React Native's `onHoverIn`/`onHoverOut` on `Pressable` does NOT fire on native iOS/iPad — the underlying W3C pointer events are behind disabled experimental flags. For hover-to-show UI (kebab menus, action buttons), use `isHovered || isNative || isCompact` so the controls are always visible on native and hover-to-show on web.\n- **Don't use Platform.OS as a proxy for layout capabilities.** Use breakpoints for layout decisions, not platform checks.\n- **Import `isWeb`/`isNative` from `@/constants/platform`.** Never write `const isWeb = Platform.OS === \"web\"` locally.\n\n## Debugging\n\nFind the complete daemon logs and traces in the $PASEO_HOME/daemon.log\n"},"files":{"CLAUDE.md":"# CLAUDE.md\n\nPaseo is a mobile app for monitoring and controlling your local AI coding agents from anywhere. Your dev environment, in your pocket. Connects directly to your actual development environment — your code stays on your machine.\n\n**Supported agents:** Claude Code, Codex, GitHub Copilot, OpenCode, and Pi.\n\n## Repository map\n\nThis is an npm workspace monorepo:\n\n- `packages/server` — Daemon: agent lifecycle, WebSocket API, MCP server\n- `packages/app` — Mobile + web client (Expo)\n- `packages/cli` — Docker-style CLI (`paseo run/ls/logs/wait`)\n- `packages/relay` — E2E encrypted relay for remote access\n- `packages/desktop` — Electron desktop wrapper\n- `packages/website` — Marketing site (paseo.sh)\n\n## Docs\n\n`docs/` is the source of truth for system-level and process-level knowledge. **\"The docs\", \"check the docs\", or \"check the X docs\" always mean this directory — not the web.** Look here before fetching anything online; the docs capture gotchas and conventions you cannot derive from the code or external sources.\n\nAt the start of non-trivial work, list `docs/` and skim anything relevant to the task.\n\n| Doc                                                                | What's in it                                                                                                                   |\n| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |\n| [docs/product.md](docs/product.md)                                 | What Paseo is, who it's for, where it's going                                                                                  |\n| [docs/architecture.md](docs/architecture.md)                       | System design, package layering, WebSocket protocol, agent lifecycle, data flow                                                |\n| [docs/agent-lifecycle.md](docs/agent-lifecycle.md)                 | Agent states, parent/child relationships, archive semantics, tabs vs archive, subagents track                                  |\n| [docs/data-model.md](docs/data-model.md)                           | File-based JSON persistence, Zod schemas, atomic writes, no migrations                                                         |\n| [docs/glossary.md](docs/glossary.md)                               | Authoritative terminology — UI label wins, no synonyms                                                                         |\n| [docs/coding-standards.md](docs/coding-standards.md)               | Type hygiene, error handling, state design, React patterns, file organization                                                  |\n| [docs/design.md](docs/design.md)                                   | Design system — tokens, buttons, hierarchy, density, alignment rails, states, what's forbidden                                 |\n| [docs/forms.md](docs/forms.md)                                     | Form architecture — non-React form model, form kit, load-state gating; the schedule form is the golden example                 |\n| [docs/hover.md](docs/hover.md)                                     | Hover — the canonical pattern (plain View + onPointerEnter/Leave, separate inner Pressable) and the three ways agents break it |\n| [docs/unistyles.md](docs/unistyles.md)                             | Unistyles gotchas — `useUnistyles()` is forbidden, alternatives in order                                                       |\n| [docs/floating-panels.md](docs/floating-panels.md)                 | Anchored popovers — Portal/Modal escape for Android, lifecycle gates, keyboard-shared-value, status-bar offset, the flash      |\n| [docs/menus.md](docs/menus.md)                                     | The menu engine — popover vs sheet, submenu pages, hover intent, when a decision earns a submenu                               |\n| [docs/expo-router.md](docs/expo-router.md)                         | Expo Router route ownership, startup restore, and native blank-screen gotchas                                                  |\n| [docs/file-icons.md](docs/file-icons.md)                           | Material icon theme integration for the file explorer                                                                          |\n| [docs/providers.md](docs/providers.md)                             | Adding a new agent provider end-to-end                                                                                         |\n| [docs/forge-providers.md](docs/forge-providers.md)                 | Adding a git forge: registry/manifest, drop-in checklist, self-host/GHES, the two facts tiers                                  |\n| [docs/custom-providers.md](docs/custom-providers.md)               | Custom provider config: Z.AI, Alibaba/Qwen, ACP agents, profiles, custom binaries                                              |\n| [docs/plugins.md](docs/plugins.md)                                 | Local plugin manifest, directory source config, RPCs, native surfaces, and attachment sources                                  |\n| [docs/service-proxy.md](docs/service-proxy.md)                     | Service proxy: exposing workspace scripts at public URLs, DNS setup, reverse proxy config                                      |\n| [docs/development.md](docs/development.md)                         | Dev server, build sync gotchas, CLI reference, agent state, Playwright MCP                                                     |\n| [docs/rpc-namespacing.md](docs/rpc-namespacing.md)                 | WebSocket RPC naming convention — dotted namespaces and `.request`/`.response` pairs                                           |\n| [docs/protocol-compatibility.md](docs/protocol-compatibility.md)   | Why app/daemon versions drift, protocol vs feature contract, capability gating, COMPAT tagging                                 |\n| [docs/protocol-validation.md](docs/protocol-validation.md)         | zod-aot generated inbound WebSocket validation, patched compiler regressions, schema-purity rules                              |\n| [docs/terminal-performance.md](docs/terminal-performance.md)       | Terminal latency pipeline, coalescing/backpressure invariants, benchmark + perf spec usage                                     |\n| [docs/file-observation.md](docs/file-observation.md)               | Recursive watcher ownership, Linux constraints, teardown invariants, and Parcel comparison                                     |\n| [docs/testing.md](docs/testing.md)                                 | TDD workflow, determinism, real dependencies over mocks, test organization                                                     |\n| [docs/qa.md](docs/qa.md)                                           | QA evidence bar for pull requests — platform matrix, version drift, performance, UI proof                                      |\n| [docs/mobile-testing.md](docs/mobile-testing.md)                   | Maestro and mobile test workflows                                                                                              |\n| [docs/mobile-panels.md](docs/mobile-panels.md)                     | Compact left/center/right panel ownership, worklet motion, gesture revisions, and Fabric constraints                           |\n| [docs/ad-hoc-daemon-testing.md](docs/ad-hoc-daemon-testing.md)     | Isolated in-process daemon test harness                                                                                        |\n| [docs/browser-capture-harness.md](docs/browser-capture-harness.md) | Real-Electron browser screenshot harness and compositor-surface gotcha                                                         |\n| [docs/android.md](docs/android.md)                                 | App variants, local/cloud builds, EAS workflows                                                                                |\n| [docs/docker.md](docs/docker.md)                                   | Running the daemon and bundled web UI in Docker, volumes, agent images, security                                               |\n| [docs/release.md](docs/release.md)                                 | Release playbook, draft releases, completion checklist                                                                         |\n| [docs/terminal-activity.md](docs/terminal-activity.md)             | Terminal activity indicators — source-agnostic tracker, agent hook reporting, adding a new hook provider                       |\n| [SECURITY.md](SECURITY.md)                                         | Relay threat model, E2E encryption, DNS rebinding, agent auth                                                                  |\n| [public-docs/hub/security.md](public-docs/hub/security.md)         | Public Hub guide — trust boundaries, untrusted triggers, provider controls, and output authority                               |\n\n### Writing docs\n\n- **Integrate, don't append.** Find the doc that owns the subject and rewrite the part that is now wrong. The standard failure is finishing a task and adding a paragraph to the bottom of the closest-looking doc; ten tasks later the doc is a pile of paragraphs in discovery order. `docs/custom-providers.md` is what that looks like.\n- **Don't document logic.** Prose that restates code drifts from the code and loses. Write down what the code can't tell you: why something is shaped the way it is, the gotcha that cost an afternoon, conventions nothing enforces, constraints that span packages or versions. If a reader could get it in two minutes by opening the file, cut it.\n- **One fact, one doc.** Every other mention is a link. If you are about to write the same paragraph in two docs, one of them is a link.\n- **Respect the layers.** `CONTRIBUTING.md` and this file name things and link out. Activity docs like `docs/qa.md` and `docs/testing.md` set the bar for a kind of work. Subject docs like `docs/unistyles.md` own one thing completely. A layer never re-explains the one below it.\n- **One subject per doc.** If the subject doesn't fit in a sentence, split the doc. A section per provider, vendor, or platform is a table plus one worked example.\n- **Delete.** Obsolete sections go. Prefer a `packages/app/src/thing.ts:120` reference over a pasted block.\n- **New doc?** Add a row to the table above and link it from the docs that should send readers there.\n- Code-level facts belong in comments next to the code, not here.\n\n### Doc voice\n\nPlain and short. Second person. State the rule, then the reason when the reason isn't obvious. Match the doc you're editing.\n\nDo not:\n\n- Write a sentence to land a point. \"It's not X, it's Y\", \"That's not a Z, that's a W\", and every other setup-and-punchline shape.\n- Add a clause that only asserts importance: \"and that matters\", \"which is what keeps it working\", \"this is critical\".\n- Use \"honest\", \"robust\", \"seamless\", \"powerful\", \"simply\", \"just\", \"delightful\".\n- Restate something you already said, in different words, for emphasis.\n- Hedge with \"generally\", \"typically\", or \"you may want to\" when the answer is \"do this\".\n- Clear your throat: \"It's worth noting that\", \"In order to\", \"This section covers\".\n\n## Quick start\n\n```bash\nnpm run dev                          # Start the dev daemon\nnpm run dev:app                      # Start Expo against the dev daemon\nnpm run dev:desktop                  # Start Electron desktop dev\nnpm run cli -- ls -a -g              # List all agents\nnpm run cli -- daemon status         # Check daemon status\nnpm run typecheck                    # Always run after changes\nnpm run lint                         # Always run after changes\nnpm run format                       # Auto-format with Biome\nnpm run format:check                 # Check formatting without writing\n```\n\nRepo dev commands use checkout-local state by default. In this checkout, `PASEO_HOME` resolves to `.dev/paseo-home`, and `npm run cli -- ...` targets that same dev home automatically. The packaged desktop app and production-style daemon keep using `~/.paseo` on port `6767`.\n\nSee [docs/development.md](docs/development.md) for full setup, build sync requirements, and debugging.\n\n## Critical rules\n\n- **NEVER restart the main Paseo daemon on port 6767 without permission** — it manages all running agents. If you're an agent, restarting it kills your own process.\n- **NEVER assume a timeout means the service needs restarting** — timeouts can be transient.\n- **NEVER add auth checks to tests** — agent providers handle their own auth.\n- **Before changing app routes, startup routing, remembered workspace restore, or active workspace selection, read [docs/expo-router.md](docs/expo-router.md).**\n- **NEVER run the full test suite locally.** The test suites are heavy and will freeze the machine, especially if multiple agents run them in parallel. Rules:\n  - Run only the specific test file you changed: `npx vitest run <file> --bail=1`\n  - Never run `npm run test` for an entire workspace unless explicitly asked.\n  - If you must run a broad suite, pipe output to a file and read it afterward: `npx vitest run <file> --bail=1 > /tmp/test-output.txt 2>&1` then read the file.\n  - Never re-run a test suite that another agent already ran and reported green — trust the result.\n  - For full suite verification, push to CI and check GitHub Actions instead.\n- **Always run typecheck and lint after every change.**\n- **Build workspace packages before diagnosing cross-package type errors.** This repo consumes generated declarations across workspaces. If typecheck fails in a package that depends on another workspace, rebuild the owning stack first so `dist` declarations are current:\n  - `npm run build:client` — rebuild protocol and client declarations.\n  - `npm run build:server` — rebuild highlight, relay, protocol, client, server, and CLI when server/CLI types may be stale.\n  - Do not patch inferred callback parameters or add local duplicate types just to silence stale declaration errors.\n- **Run `npm run format` before committing.** This repo uses Biome for formatting. Do not manually fix formatting — let the formatter handle it.\n- **Always use npm scripts for linting and formatting.** Do not run tools directly with `npx eslint`, `npx oxfmt`, `npx oxlint`, or package-local binaries. For targeted checks, pass file paths through the npm script:\n  - `npm run lint -- packages/app/src/components/message.tsx`\n  - `npm run format:files -- CLAUDE.md packages/app/src/components/message.tsx`\n- **The protocol stays backward-compatible. Features don't have to.** Read [docs/protocol-compatibility.md](docs/protocol-compatibility.md) before touching `packages/protocol`. The short version:\n  - **Protocol contract (always):** an old client parses messages from a new daemon, and a new daemon parses messages from an old client. New fields are optional; never narrow, never remove, never require. Wire schemas stay pure — no `.transform()`, `.catch()`, or `.preprocess()`.\n  - **Feature contract (per-feature):** gate the capability once on `server_info.features.*`, then run the feature or tell the user to update the host. No fallback paths, no defensive branches.\n  - **Every shim is tagged.** `// COMPAT(name): added in vX, remove after <date>` at the site that has to be deleted. `rg \"COMPAT\\(\"` is the cleanup backlog; untagged back-compat is permanent by accident.\n  - **New RPCs use dotted namespaces with direction suffixes.** Follow [docs/rpc-namespacing.md](docs/rpc-namespacing.md): `domain.provider.operation.request` pairs with `domain.provider.operation.response`. Existing flat RPC names will migrate over time; don't add new ones.\n\n## Platform gating\n\nThe app runs on iOS, Android, web (browser), and web (Electron desktop). Code is cross-platform by default. Gate only when you must. Import gates from `@/constants/platform`.\n\n### The four gates\n\n| Gate                       | Type      | When to use                                                                                                                 |\n| -------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------- |\n| `isWeb`                    | constant  | DOM APIs — `document`, `window`, `<div>`, `addEventListener`, `ResizeObserver`. This is the **exception**, not the default. |\n| `isNative`                 | constant  | Native-only APIs — Haptics, `StatusBar.currentHeight`, push tokens, camera/scanner, `expo-av`.                              |\n| `getIsElectron()`          | cached fn | Desktop wrapper features — file dialogs, titlebar drag region, daemon management, app updates, dock badges.                 |\n| `useIsCompactFormFactor()` | hook      | Layout decisions — sidebar overlay vs pinned, modal vs full screen, single-panel vs split. From `@/constants/layout`.       |\n\n### Decision matrix\n\n| I need to...                                                   | Use                                                                       |\n| -------------------------------------------------------------- | ------------------------------------------------------------------------- |\n| Access DOM (`document`, `window`, `<div>`, `addEventListener`) | `if (isWeb)`                                                              |\n| Use a native-only API (Haptics, push tokens, camera)           | `if (isNative)`                                                           |\n| Use an Electron bridge (file dialog, titlebar, updates)        | `if (getIsElectron())`                                                    |\n| Switch layout between phone and tablet/desktop                 | `useIsCompactFormFactor()`                                                |\n| Show something on hover, always-visible on native              | `isHovered \\|\\| isNative \\|\\| isCompact` (hover only works on web)        |\n| Gate to iOS or Android specifically                            | `Platform.OS === \"ios\"` / `Platform.OS === \"android\"` (rare, keep inline) |\n\n### Rules\n\n- **Default is cross-platform.** Don't gate unless you have a specific reason.\n- **Prefer Metro file extensions over `if` statements.** When a module has fundamentally different implementations per platform, use `.web.ts` / `.native.ts` file extensions instead of runtime `if (isWeb)` branches. Metro resolves the correct file at build time — the unused platform code is never bundled. Reserve `if (isWeb)` for small, inline checks (a single line or a few props). If you find yourself writing a large `if (isWeb) { ... } else { ... }` block, split into separate files instead.\n  ```\n  hooks/\n    use-audio-recorder.web.ts    ← uses Web Audio API\n    use-audio-recorder.native.ts ← uses expo-audio\n  ```\n  Import as `@/hooks/use-audio-recorder` — Metro picks the right file automatically.\n- **Use `.electron.ts` / `.electron.tsx` for Electron-only web modules.** Electron is still the Metro `web` platform, but desktop dev/build sets `PASEO_WEB_PLATFORM=electron`, so Metro first looks for `.electron.*` files and falls back to normal `.web.*` files. Use this when the implementation depends on Electron-only behavior such as `webviewTag`, desktop preload APIs, or the Electron bridge. Keep plain browser web in `.web.*`, and keep native fallbacks in the base file or `.native.*`.\n  ```\n  desktop/browser/pane/\n    index.electron.tsx ← Electron <webview> implementation\n    index.web.tsx      ← plain web fallback\n    index.tsx          ← native fallback\n  ```\n  Import as `@/desktop/browser/pane` — Electron desktop gets the `.electron.tsx` file, browser web gets `.web.tsx`, and native gets the native/base implementation.\n- **NEVER use raw DOM APIs without `isWeb` guard.** DOM APIs crash native. Casting a RN ref to `HTMLElement` is a red flag — ensure the block is web-only.\n- **NEVER use `onPointerEnter`/`onPointerLeave`.** They don't fire on native iOS.\n- **Hover only works on web.** React Native's `onHoverIn`/`onHoverOut` on `Pressable` does NOT fire on native iOS/iPad — the underlying W3C pointer events are behind disabled experimental flags. For hover-to-show UI (kebab menus, action buttons), use `isHovered || isNative || isCompact` so the controls are always visible on native and hover-to-show on web.\n- **Don't use Platform.OS as a proxy for layout capabilities.** Use breakpoints for layout decisions, not platform checks.\n- **Import `isWeb`/`isNative` from `@/constants/platform`.** Never write `const isWeb = Platform.OS === \"web\"` locally.\n\n## Debugging\n\nFind the complete daemon logs and traces in the $PASEO_HOME/daemon.log\n"},"items":[{"name":"CLAUDE.md","path":"CLAUDE.md","title":"CLAUDE.md","content":"# CLAUDE.md\n\nPaseo is a mobile app for monitoring and controlling your local AI coding agents from anywhere. Your dev environment, in your pocket. Connects directly to your actual development environment — your code stays on your machine.\n\n**Supported agents:** Claude Code, Codex, GitHub Copilot, OpenCode, and Pi.\n\n## Repository map\n\nThis is an npm workspace monorepo:\n\n- `packages/server` — Daemon: agent lifecycle, WebSocket API, MCP server\n- `packages/app` — Mobile + web client (Expo)\n- `packages/cli` — Docker-style CLI (`paseo run/ls/logs/wait`)\n- `packages/relay` — E2E encrypted relay for remote access\n- `packages/desktop` — Electron desktop wrapper\n- `packages/website` — Marketing site (paseo.sh)\n\n## Docs\n\n`docs/` is the source of truth for system-level and process-level knowledge. **\"The docs\", \"check the docs\", or \"check the X docs\" always mean this directory — not the web.** Look here before fetching anything online; the docs capture gotchas and conventions you cannot derive from the code or external sources.\n\nAt the start of non-trivial work, list `docs/` and skim anything relevant to the task.\n\n| Doc                                                                | What's in it                                                                                                                   |\n| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |\n| [docs/product.md](docs/product.md)                                 | What Paseo is, who it's for, where it's going                                                                                  |\n| [docs/architecture.md](docs/architecture.md)                       | System design, package layering, WebSocket protocol, agent lifecycle, data flow                                                |\n| [docs/agent-lifecycle.md](docs/agent-lifecycle.md)                 | Agent states, parent/child relationships, archive semantics, tabs vs archive, subagents track                                  |\n| [docs/data-model.md](docs/data-model.md)                           | File-based JSON persistence, Zod schemas, atomic writes, no migrations                                                         |\n| [docs/glossary.md](docs/glossary.md)                               | Authoritative terminology — UI label wins, no synonyms                                                                         |\n| [docs/coding-standards.md](docs/coding-standards.md)               | Type hygiene, error handling, state design, React patterns, file organization                                                  |\n| [docs/design.md](docs/design.md)                                   | Design system — tokens, buttons, hierarchy, density, alignment rails, states, what's forbidden                                 |\n| [docs/forms.md](docs/forms.md)                                     | Form architecture — non-React form model, form kit, load-state gating; the schedule form is the golden example                 |\n| [docs/hover.md](docs/hover.md)                                     | Hover — the canonical pattern (plain View + onPointerEnter/Leave, separate inner Pressable) and the three ways agents break it |\n| [docs/unistyles.md](docs/unistyles.md)                             | Unistyles gotchas — `useUnistyles()` is forbidden, alternatives in order                                                       |\n| [docs/floating-panels.md](docs/floating-panels.md)                 | Anchored popovers — Portal/Modal escape for Android, lifecycle gates, keyboard-shared-value, status-bar offset, the flash      |\n| [docs/menus.md](docs/menus.md)                                     | The menu engine — popover vs sheet, submenu pages, hover intent, when a decision earns a submenu                               |\n| [docs/expo-router.md](docs/expo-router.md)                         | Expo Router route ownership, startup restore, and native blank-screen gotchas                                                  |\n| [docs/file-icons.md](docs/file-icons.md)                           | Material icon theme integration for the file explorer                                                                          |\n| [docs/providers.md](docs/providers.md)                             | Adding a new agent provider end-to-end                                                                                         |\n| [docs/forge-providers.md](docs/forge-providers.md)                 | Adding a git forge: registry/manifest, drop-in checklist, self-host/GHES, the two facts tiers                                  |\n| [docs/custom-providers.md](docs/custom-providers.md)               | Custom provider config: Z.AI, Alibaba/Qwen, ACP agents, profiles, custom binaries                                              |\n| [docs/plugins.md](docs/plugins.md)                                 | Local plugin manifest, directory source config, RPCs, native surfaces, and attachment sources                                  |\n| [docs/service-proxy.md](docs/service-proxy.md)                     | Service proxy: exposing workspace scripts at public URLs, DNS setup, reverse proxy config                                      |\n| [docs/development.md](docs/development.md)                         | Dev server, build sync gotchas, CLI reference, agent state, Playwright MCP                                                     |\n| [docs/rpc-namespacing.md](docs/rpc-namespacing.md)                 | WebSocket RPC naming convention — dotted namespaces and `.request`/`.response` pairs                                           |\n| [docs/protocol-compatibility.md](docs/protocol-compatibility.md)   | Why app/daemon versions drift, protocol vs feature contract, capability gating, COMPAT tagging                                 |\n| [docs/protocol-validation.md](docs/protocol-validation.md)         | zod-aot generated inbound WebSocket validation, patched compiler regressions, schema-purity rules                              |\n| [docs/terminal-performance.md](docs/terminal-performance.md)       | Terminal latency pipeline, coalescing/backpressure invariants, benchmark + perf spec usage                                     |\n| [docs/file-observation.md](docs/file-observation.md)               | Recursive watcher ownership, Linux constraints, teardown invariants, and Parcel comparison                                     |\n| [docs/testing.md](docs/testing.md)                                 | TDD workflow, determinism, real dependencies over mocks, test organization                                                     |\n| [docs/qa.md](docs/qa.md)                                           | QA evidence bar for pull requests — platform matrix, version drift, performance, UI proof                                      |\n| [docs/mobile-testing.md](docs/mobile-testing.md)                   | Maestro and mobile test workflows                                                                                              |\n| [docs/mobile-panels.md](docs/mobile-panels.md)                     | Compact left/center/right panel ownership, worklet motion, gesture revisions, and Fabric constraints                           |\n| [docs/ad-hoc-daemon-testing.md](docs/ad-hoc-daemon-testing.md)     | Isolated in-process daemon test harness                                                                                        |\n| [docs/browser-capture-harness.md](docs/browser-capture-harness.md) | Real-Electron browser screenshot harness and compositor-surface gotcha                                                         |\n| [docs/android.md](docs/android.md)                                 | App variants, local/cloud builds, EAS workflows                                                                                |\n| [docs/docker.md](docs/docker.md)                                   | Running the daemon and bundled web UI in Docker, volumes, agent images, security                                               |\n| [docs/release.md](docs/release.md)                                 | Release playbook, draft releases, completion checklist                                                                         |\n| [docs/terminal-activity.md](docs/terminal-activity.md)             | Terminal activity indicators — source-agnostic tracker, agent hook reporting, adding a new hook provider                       |\n| [SECURITY.md](SECURITY.md)                                         | Relay threat model, E2E encryption, DNS rebinding, agent auth                                                                  |\n| [public-docs/hub/security.md](public-docs/hub/security.md)         | Public Hub guide — trust boundaries, untrusted triggers, provider controls, and output authority                               |\n\n### Writing docs\n\n- **Integrate, don't append.** Find the doc that owns the subject and rewrite the part that is now wrong. The standard failure is finishing a task and adding a paragraph to the bottom of the closest-looking doc; ten tasks later the doc is a pile of paragraphs in discovery order. `docs/custom-providers.md` is what that looks like.\n- **Don't document logic.** Prose that restates code drifts from the code and loses. Write down what the code can't tell you: why something is shaped the way it is, the gotcha that cost an afternoon, conventions nothing enforces, constraints that span packages or versions. If a reader could get it in two minutes by opening the file, cut it.\n- **One fact, one doc.** Every other mention is a link. If you are about to write the same paragraph in two docs, one of them is a link.\n- **Respect the layers.** `CONTRIBUTING.md` and this file name things and link out. Activity docs like `docs/qa.md` and `docs/testing.md` set the bar for a kind of work. Subject docs like `docs/unistyles.md` own one thing completely. A layer never re-explains the one below it.\n- **One subject per doc.** If the subject doesn't fit in a sentence, split the doc. A section per provider, vendor, or platform is a table plus one worked example.\n- **Delete.** Obsolete sections go. Prefer a `packages/app/src/thing.ts:120` reference over a pasted block.\n- **New doc?** Add a row to the table above and link it from the docs that should send readers there.\n- Code-level facts belong in comments next to the code, not here.\n\n### Doc voice\n\nPlain and short. Second person. State the rule, then the reason when the reason isn't obvious. Match the doc you're editing.\n\nDo not:\n\n- Write a sentence to land a point. \"It's not X, it's Y\", \"That's not a Z, that's a W\", and every other setup-and-punchline shape.\n- Add a clause that only asserts importance: \"and that matters\", \"which is what keeps it working\", \"this is critical\".\n- Use \"honest\", \"robust\", \"seamless\", \"powerful\", \"simply\", \"just\", \"delightful\".\n- Restate something you already said, in different words, for emphasis.\n- Hedge with \"generally\", \"typically\", or \"you may want to\" when the answer is \"do this\".\n- Clear your throat: \"It's worth noting that\", \"In order to\", \"This section covers\".\n\n## Quick start\n\n```bash\nnpm run dev                          # Start the dev daemon\nnpm run dev:app                      # Start Expo against the dev daemon\nnpm run dev:desktop                  # Start Electron desktop dev\nnpm run cli -- ls -a -g              # List all agents\nnpm run cli -- daemon status         # Check daemon status\nnpm run typecheck                    # Always run after changes\nnpm run lint                         # Always run after changes\nnpm run format                       # Auto-format with Biome\nnpm run format:check                 # Check formatting without writing\n```\n\nRepo dev commands use checkout-local state by default. In this checkout, `PASEO_HOME` resolves to `.dev/paseo-home`, and `npm run cli -- ...` targets that same dev home automatically. The packaged desktop app and production-style daemon keep using `~/.paseo` on port `6767`.\n\nSee [docs/development.md](docs/development.md) for full setup, build sync requirements, and debugging.\n\n## Critical rules\n\n- **NEVER restart the main Paseo daemon on port 6767 without permission** — it manages all running agents. If you're an agent, restarting it kills your own process.\n- **NEVER assume a timeout means the service needs restarting** — timeouts can be transient.\n- **NEVER add auth checks to tests** — agent providers handle their own auth.\n- **Before changing app routes, startup routing, remembered workspace restore, or active workspace selection, read [docs/expo-router.md](docs/expo-router.md).**\n- **NEVER run the full test suite locally.** The test suites are heavy and will freeze the machine, especially if multiple agents run them in parallel. Rules:\n  - Run only the specific test file you changed: `npx vitest run <file> --bail=1`\n  - Never run `npm run test` for an entire workspace unless explicitly asked.\n  - If you must run a broad suite, pipe output to a file and read it afterward: `npx vitest run <file> --bail=1 > /tmp/test-output.txt 2>&1` then read the file.\n  - Never re-run a test suite that another agent already ran and reported green — trust the result.\n  - For full suite verification, push to CI and check GitHub Actions instead.\n- **Always run typecheck and lint after every change.**\n- **Build workspace packages before diagnosing cross-package type errors.** This repo consumes generated declarations across workspaces. If typecheck fails in a package that depends on another workspace, rebuild the owning stack first so `dist` declarations are current:\n  - `npm run build:client` — rebuild protocol and client declarations.\n  - `npm run build:server` — rebuild highlight, relay, protocol, client, server, and CLI when server/CLI types may be stale.\n  - Do not patch inferred callback parameters or add local duplicate types just to silence stale declaration errors.\n- **Run `npm run format` before committing.** This repo uses Biome for formatting. Do not manually fix formatting — let the formatter handle it.\n- **Always use npm scripts for linting and formatting.** Do not run tools directly with `npx eslint`, `npx oxfmt`, `npx oxlint`, or package-local binaries. For targeted checks, pass file paths through the npm script:\n  - `npm run lint -- packages/app/src/components/message.tsx`\n  - `npm run format:files -- CLAUDE.md packages/app/src/components/message.tsx`\n- **The protocol stays backward-compatible. Features don't have to.** Read [docs/protocol-compatibility.md](docs/protocol-compatibility.md) before touching `packages/protocol`. The short version:\n  - **Protocol contract (always):** an old client parses messages from a new daemon, and a new daemon parses messages from an old client. New fields are optional; never narrow, never remove, never require. Wire schemas stay pure — no `.transform()`, `.catch()`, or `.preprocess()`.\n  - **Feature contract (per-feature):** gate the capability once on `server_info.features.*`, then run the feature or tell the user to update the host. No fallback paths, no defensive branches.\n  - **Every shim is tagged.** `// COMPAT(name): added in vX, remove after <date>` at the site that has to be deleted. `rg \"COMPAT\\(\"` is the cleanup backlog; untagged back-compat is permanent by accident.\n  - **New RPCs use dotted namespaces with direction suffixes.** Follow [docs/rpc-namespacing.md](docs/rpc-namespacing.md): `domain.provider.operation.request` pairs with `domain.provider.operation.response`. Existing flat RPC names will migrate over time; don't add new ones.\n\n## Platform gating\n\nThe app runs on iOS, Android, web (browser), and web (Electron desktop). Code is cross-platform by default. Gate only when you must. Import gates from `@/constants/platform`.\n\n### The four gates\n\n| Gate                       | Type      | When to use                                                                                                                 |\n| -------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------- |\n| `isWeb`                    | constant  | DOM APIs — `document`, `window`, `<div>`, `addEventListener`, `ResizeObserver`. This is the **exception**, not the default. |\n| `isNative`                 | constant  | Native-only APIs — Haptics, `StatusBar.currentHeight`, push tokens, camera/scanner, `expo-av`.                              |\n| `getIsElectron()`          | cached fn | Desktop wrapper features — file dialogs, titlebar drag region, daemon management, app updates, dock badges.                 |\n| `useIsCompactFormFactor()` | hook      | Layout decisions — sidebar overlay vs pinned, modal vs full screen, single-panel vs split. From `@/constants/layout`.       |\n\n### Decision matrix\n\n| I need to...                                                   | Use                                                                       |\n| -------------------------------------------------------------- | ------------------------------------------------------------------------- |\n| Access DOM (`document`, `window`, `<div>`, `addEventListener`) | `if (isWeb)`                                                              |\n| Use a native-only API (Haptics, push tokens, camera)           | `if (isNative)`                                                           |\n| Use an Electron bridge (file dialog, titlebar, updates)        | `if (getIsElectron())`                                                    |\n| Switch layout between phone and tablet/desktop                 | `useIsCompactFormFactor()`                                                |\n| Show something on hover, always-visible on native              | `isHovered \\|\\| isNative \\|\\| isCompact` (hover only works on web)        |\n| Gate to iOS or Android specifically                            | `Platform.OS === \"ios\"` / `Platform.OS === \"android\"` (rare, keep inline) |\n\n### Rules\n\n- **Default is cross-platform.** Don't gate unless you have a specific reason.\n- **Prefer Metro file extensions over `if` statements.** When a module has fundamentally different implementations per platform, use `.web.ts` / `.native.ts` file extensions instead of runtime `if (isWeb)` branches. Metro resolves the correct file at build time — the unused platform code is never bundled. Reserve `if (isWeb)` for small, inline checks (a single line or a few props). If you find yourself writing a large `if (isWeb) { ... } else { ... }` block, split into separate files instead.\n  ```\n  hooks/\n    use-audio-recorder.web.ts    ← uses Web Audio API\n    use-audio-recorder.native.ts ← uses expo-audio\n  ```\n  Import as `@/hooks/use-audio-recorder` — Metro picks the right file automatically.\n- **Use `.electron.ts` / `.electron.tsx` for Electron-only web modules.** Electron is still the Metro `web` platform, but desktop dev/build sets `PASEO_WEB_PLATFORM=electron`, so Metro first looks for `.electron.*` files and falls back to normal `.web.*` files. Use this when the implementation depends on Electron-only behavior such as `webviewTag`, desktop preload APIs, or the Electron bridge. Keep plain browser web in `.web.*`, and keep native fallbacks in the base file or `.native.*`.\n  ```\n  desktop/browser/pane/\n    index.electron.tsx ← Electron <webview> implementation\n    index.web.tsx      ← plain web fallback\n    index.tsx          ← native fallback\n  ```\n  Import as `@/desktop/browser/pane` — Electron desktop gets the `.electron.tsx` file, browser web gets `.web.tsx`, and native gets the native/base implementation.\n- **NEVER use raw DOM APIs without `isWeb` guard.** DOM APIs crash native. Casting a RN ref to `HTMLElement` is a red flag — ensure the block is web-only.\n- **NEVER use `onPointerEnter`/`onPointerLeave`.** They don't fire on native iOS.\n- **Hover only works on web.** React Native's `onHoverIn`/`onHoverOut` on `Pressable` does NOT fire on native iOS/iPad — the underlying W3C pointer events are behind disabled experimental flags. For hover-to-show UI (kebab menus, action buttons), use `isHovered || isNative || isCompact` so the controls are always visible on native and hover-to-show on web.\n- **Don't use Platform.OS as a proxy for layout capabilities.** Use breakpoints for layout decisions, not platform checks.\n- **Import `isWeb`/`isNative` from `@/constants/platform`.** Never write `const isWeb = Platform.OS === \"web\"` locally.\n\n## Debugging\n\nFind the complete daemon logs and traces in the $PASEO_HOME/daemon.log\n","category":"root","tokens":5154}]}