mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Add `packages/app/src/constants/platform.ts` with canonical gates (isWeb, isNative, getIsElectron) and refactor all ~100 ad-hoc Platform.OS checks across 69 files to use shared imports. Fix sidebar hover crash on native (onPointerEnter → onHoverIn), fix tooltip to use breakpoint instead of platform detection, make sidebar action buttons always visible on native where hover doesn't work, and document the platform gating decision matrix in CLAUDE.md.
6.5 KiB
6.5 KiB
CLAUDE.md
Paseo 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.
Supported agents: Claude Code, Codex, and OpenCode.
Repository map
This is an npm workspace monorepo:
packages/server— Daemon: agent lifecycle, WebSocket API, MCP serverpackages/app— Mobile + web client (Expo)packages/cli— Docker-style CLI (paseo run/ls/logs/wait)packages/relay— E2E encrypted relay for remote accesspackages/desktop— Electron desktop wrapperpackages/website— Marketing site (paseo.sh)
Documentation
| Doc | What's in it |
|---|---|
| docs/ARCHITECTURE.md | System design, package layering, WebSocket protocol, agent lifecycle, data flow |
| docs/CODING_STANDARDS.md | Type hygiene, error handling, state design, React patterns, file organization |
| docs/TESTING.md | TDD workflow, determinism, real dependencies over mocks, test organization |
| docs/DEVELOPMENT.md | Dev server, build sync gotchas, CLI reference, agent state, Playwright MCP |
| docs/RELEASE.md | Release playbook, draft releases, completion checklist |
| docs/ANDROID.md | App variants, local/cloud builds, EAS workflows |
| docs/DESIGN.md | How to design features before implementation |
| SECURITY.md | Relay threat model, E2E encryption, DNS rebinding, agent auth |
Quick start
npm run dev # Start daemon + Expo in Tmux
npm run cli -- ls -a -g # List all agents
npm run cli -- daemon status # Check daemon status
npm run typecheck # Always run after changes
npm run format # Auto-format with Biome
npm run format:check # Check formatting without writing
See docs/DEVELOPMENT.md for full setup, build sync requirements, and debugging.
Critical rules
- 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.
- NEVER assume a timeout means the service needs restarting — timeouts can be transient.
- NEVER add auth checks to tests — agent providers handle their own auth.
- Always run typecheck after every change.
- Run
npm run formatbefore committing. This repo uses Biome for formatting. Do not manually fix formatting — let the formatter handle it. - NEVER make breaking changes to WebSocket or message schemas. The primary compatibility path is old mobile app clients talking to newly updated daemons. Users update desktop and daemon first, then keep running the old app for a while. Every schema change MUST be backward-compatible for old clients against new daemons:
- New fields: always
.optional()with a sensible default or.transform()fallback. - Never change a field from optional to required.
- Never remove a field — deprecate it (keep accepting it, stop sending it).
- Never narrow a field's type (e.g.
string→enum,nullable→ non-null). - Test with: "does a 6-month-old client still parse this?" and "does a 6-month-old daemon still send something this client accepts?"
- New fields: always
Platform gating
The 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.
The four gates
| Gate | Type | When to use |
|---|---|---|
isWeb |
constant | DOM APIs — document, window, <div>, addEventListener, ResizeObserver. This is the exception, not the default. |
isNative |
constant | Native-only APIs — Haptics, StatusBar.currentHeight, push tokens, camera/scanner, expo-av. |
getIsElectron() |
cached fn | Desktop wrapper features — file dialogs, titlebar drag region, daemon management, app updates, dock badges. |
useIsCompactFormFactor() |
hook | Layout decisions — sidebar overlay vs pinned, modal vs full screen, single-panel vs split. From @/constants/layout. |
Decision matrix
| I need to... | Use |
|---|---|
Access DOM (document, window, <div>, addEventListener) |
if (isWeb) |
| Use a native-only API (Haptics, push tokens, camera) | if (isNative) |
| Use an Electron bridge (file dialog, titlebar, updates) | if (getIsElectron()) |
| Switch layout between phone and tablet/desktop | useIsCompactFormFactor() |
| Show something on hover, always-visible on native | isHovered || isNative || isCompact (hover only works on web) |
| Gate to iOS or Android specifically | Platform.OS === "ios" / Platform.OS === "android" (rare, keep inline) |
Rules
- Default is cross-platform. Don't gate unless you have a specific reason.
- Prefer Metro file extensions over
ifstatements. When a module has fundamentally different implementations per platform, use.web.ts/.native.tsfile extensions instead of runtimeif (isWeb)branches. Metro resolves the correct file at build time — the unused platform code is never bundled. Reserveif (isWeb)for small, inline checks (a single line or a few props). If you find yourself writing a largeif (isWeb) { ... } else { ... }block, split into separate files instead.Import ashooks/ use-audio-recorder.web.ts ← uses Web Audio API use-audio-recorder.native.ts ← uses expo-audio@/hooks/use-audio-recorder— Metro picks the right file automatically. - NEVER use raw DOM APIs without
isWebguard. DOM APIs crash native. Casting a RN ref toHTMLElementis a red flag — ensure the block is web-only. - NEVER use
onPointerEnter/onPointerLeave. They don't fire on native iOS. - Hover only works on web. React Native's
onHoverIn/onHoverOutonPressabledoes 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), useisHovered || isNative || isCompactso the controls are always visible on native and hover-to-show on web. - Don't use Platform.OS as a proxy for layout capabilities. Use breakpoints for layout decisions, not platform checks.
- Import
isWeb/isNativefrom@/constants/platform. Never writeconst isWeb = Platform.OS === "web"locally.
Debugging
Find the complete daemon logs and traces in the $PASEO_HOME/daemon.log