Generated: 2026-01-08 | Commit: 6e613bc | Branch: main
Web, desktop, and mobile interface for OpenCode AI coding agent. Monorepo with 6 packages targeting 4 runtimes (web, desktop, VS Code, mobile).
| Layer | Technology |
|---|---|
| UI | React 19.1.1, Tailwind CSS v4, Radix UI, Zustand 5.0.8 |
| Web | Vite 7.1.2, Express 5.1.0, node-pty |
| Desktop | Tauri 2.9.4, Rust, portable-pty |
| Mobile | Expo 54, React Native 0.81, Expo Router |
| VS Code | Extension API, esbuild, Vite webview |
| SDK | @opencode-ai/sdk with SSE streaming |
packages/
├── ui/ # Shared React components, stores, hooks (273 files)
├── web/ # Express server + CLI (27 files)
├── desktop/ # Tauri app with Rust backend (34 files)
├── vscode/ # VS Code extension (24 files)
├── mobile/ # Expo/React Native iOS app (209 files)
└── shared/ # Themes, typography, spacing (13 files)
heroui-native/ # Embedded React Native UI library (external)
| Task | Location | Notes |
|---|---|---|
| Add UI component | packages/ui/src/components/ |
Auto-available to all runtimes |
| Add Zustand store | packages/ui/src/stores/ |
Use persist() middleware if needed |
| Add custom hook | packages/ui/src/hooks/ |
Prefix with use* |
| Modify themes | packages/shared/src/themes/ |
CSS vars generated automatically |
| Add API endpoint | packages/web/server/index.js |
Express routes (84 total) |
| Add Tauri command | packages/desktop/src-tauri/src/commands/ |
Rust → TS adapter in src/api/ |
| Add VS Code command | packages/vscode/src/extension.ts |
Register in package.json |
| Add mobile screen | packages/mobile/app/ |
Expo Router file-based routing |
Each runtime implements RuntimeAPIs interface from packages/ui/src/lib/api/types.ts:
packages/web/src/api/ → HTTP fetch to Express
packages/desktop/src/api/ → Tauri IPC commands
packages/vscode/webview/api/→ Extension bridge messages
packages/mobile/src/api/ → HTTP fetch to remote server
UI components access via useRuntimeAPIs() hook - platform-agnostic.
30+ Zustand stores in packages/ui/src/stores/:
- Persisted: contextStore, sessionStore, messageStore, useConfigStore, useAgentsStore
- Global access:
window.__zustand_*_store__for cross-component access - Circular dep avoidance: Dynamic imports or window globals
SDK-managed SSE with AsyncGenerator:
- 2 retry attempts, 500ms→8s exponential backoff
- Temp→real session ID swap (optimistic UI)
pendingAssistantPartsbuffering- Memory management: LRU eviction, viewport windowing
bun run dev:web # Web dev server
bun run desktop:dev # Desktop with OpenCode CLI
bun run vscode:dev # VS Code extension watch
bun run mobile:start # Expo dev server
bun run build # Build all packages
bun run type-check # TypeScript validation
bun run lint # ESLint checks| Pattern | Why |
|---|---|
as any, @ts-ignore, @ts-expect-error |
Type safety is mandatory |
| Hardcoded font sizes | Use semantic typography classes |
| Empty catch blocks | Always handle or log errors |
| Direct store imports causing circular deps | Use window.__zustand_* or dynamic imports |
| Config updates with directory param | Config is global scope |
| Awaiting model execution in multi-run | Blocks UI - only await infrastructure |
Always use semantic classes - never hardcoded sizes:
typography-markdown,typography-code,typography-ui-label- CSS vars:
--text-markdown,--text-code,--text-meta,--text-micro
Use @import "tailwindcss" syntax (not @tailwind directives).
verbatimModuleSyntax: true- explicitimport typerequiredmoduleResolution: "bundler"- Strict mode enabled
- Functional components only (no classes)
React.memofor performance-critical componentscn()utility for className composition (clsx + tailwind-merge)- Settings sections use shared boilerplate in
packages/ui/src/components/sections/shared/
| File | Lines | Purpose |
|---|---|---|
ui/stores/messageStore.ts |
2542 | Streaming, memory management, deduplication |
ui/components/chat/ModelControls.tsx |
2338 | Model/agent selection with permissions |
ui/hooks/useEventStream.ts |
1821 | SSE handling, reconnection, status tracking |
vscode/src/bridge.ts |
1353 | API proxy, file/git operations |
ui/components/chat/ChatInput.tsx |
1344 | Autocomplete, attachments, queue mode |
ui/lib/opencode/client.ts |
1341 | OpenCode SDK wrapper |
bun run type-check && bun run lint- Extend
RuntimeAPIsinterface inpackages/ui/src/lib/api/types.ts - Implement in each runtime's
api/directory - Access via
useRuntimeAPIs()hook
- Modify
packages/shared/src/themes/ - CSS vars generated automatically by
cssGenerator.ts - Test both light and dark themes
No test infrastructure exists. Quality gates:
- TypeScript type checking
- ESLint rules
- Build-time compilation
- heroui-native/: External React Native UI library embedded at root (not in packages/)
- Web server is JavaScript:
packages/web/server/index.jsis 4273 lines of JS (not TS) - Mobile doesn't share UI: Separate implementation, uses @openchamber/shared for themes only
- No CI/CD: All builds are manual, see
scripts/for release workflows
See package-specific AGENTS.md files for detailed documentation:
packages/ui/AGENTS.md- Components, stores, hookspackages/desktop/AGENTS.md- Tauri/Rust patternspackages/mobile/AGENTS.md- Expo/React Native patterns