8919913bf4
- @rowboat/spaces-protocol: the wire contract package (v0) - Harbor: one core, three faces (HTTP/WS/MCP), the §11 gate as a test suite; in-memory and Postgres (PgStore) stores; content-addressed blob store with disk and S3 drivers (spec §6 shape) - apps/x: core client + live socket, IPC, org registry; adding an org registers its MCP agent face; agent discovery via list_spaces + the spaces skill (MCP entries derived from the registry, never mcp.json) - @rowboat in topics: per-topic sessions, receipts, presence; thread context is pull-don't-push (read_topic); working chips render at the typing-indicator position; first-message invocations trigger sessions - docs: CONTRACT.md progress; tiered auth amendment (DCR + refresh are AS policy, the org stays a pure resource server) - ci: apps/harbor test job; harbor builds before every apps/x install Co-Authored-By: Claude <noreply@anthropic.com>
7.1 KiB
7.1 KiB
CLAUDE.md - AI Coding Agent Context
This file provides context for AI coding agents working on the Rowboat monorepo.
Quick Reference Commands
# Electron App (apps/x)
cd apps/x && pnpm install # Install dependencies
cd apps/x && npm run deps # Build workspace packages (shared → core → preload)
cd apps/x && npm run dev # Development mode (builds deps, runs app)
cd apps/x && npm run lint # Lint check
cd apps/x/apps/main && npm run package # Production build (.app)
cd apps/x/apps/main && npm run make # Create DMG distributable
Monorepo Structure
rowboat/
├── apps/
│ ├── x/ # Electron desktop app (focus of this doc)
│ ├── harbor/ # Spaces server + protocol (own pnpm workspace)
│ ├── rowboat/ # Next.js web dashboard
│ ├── rowboatx/ # Next.js frontend
│ ├── cli/ # CLI tool
│ ├── python-sdk/ # Python SDK
│ └── docs/ # Documentation site
├── CLAUDE.md # This file
└── README.md # User-facing readme
Electron App Architecture (apps/x)
The Electron app is a nested pnpm workspace with its own package management.
apps/x/
├── package.json # Workspace root, dev scripts
├── pnpm-workspace.yaml # Defines workspace packages
├── pnpm-lock.yaml # Lockfile
├── apps/
│ ├── main/ # Electron main process
│ │ ├── src/ # Main process source
│ │ ├── forge.config.cjs # Electron Forge config
│ │ └── bundle.mjs # esbuild bundler
│ ├── renderer/ # React UI (Vite)
│ │ ├── src/ # React components
│ │ └── vite.config.ts
│ └── preload/ # Electron preload scripts
│ └── src/
└── packages/
├── shared/ # @x/shared - Types, utilities, validators
└── core/ # @x/core - Business logic, AI, OAuth, MCP
Build Order (Dependencies)
shared (no deps)
↓
core (depends on shared)
↓
preload (depends on shared)
↓
renderer (depends on shared)
main (depends on shared, core)
The npm run deps command builds: shared → core → preload
Key Entry Points
| Component | Entry | Output |
|---|---|---|
| main | apps/main/src/main.ts |
.package/dist/main.cjs |
| renderer | apps/renderer/src/main.tsx |
apps/renderer/dist/ |
| preload | apps/preload/src/preload.ts |
apps/preload/dist/preload.js |
Build System
- Package manager: pnpm (required for
workspace:*protocol) - Main bundler: esbuild (bundles to single CommonJS file)
- Renderer bundler: Vite
- Packaging: Electron Forge
- TypeScript: ES2022 target
Why esbuild bundling?
pnpm uses symlinks for workspace packages. Electron Forge's dependency walker can't follow these symlinks. esbuild bundles everything into a single file, eliminating the need for node_modules in the packaged app.
Key Files Reference
| Purpose | File |
|---|---|
| Electron main entry | apps/x/apps/main/src/main.ts |
| React app entry | apps/x/apps/renderer/src/main.tsx |
| Forge config (packaging) | apps/x/apps/main/forge.config.cjs |
| Main process bundler | apps/x/apps/main/bundle.mjs |
| Vite config | apps/x/apps/renderer/vite.config.ts |
| Shared types | apps/x/packages/shared/src/ |
| Core business logic | apps/x/packages/core/src/ |
| Workspace config | apps/x/pnpm-workspace.yaml |
| Root scripts | apps/x/package.json |
Feature Deep-Dives
Long-form docs for specific features. Read the relevant file before making changes in that area — it has the full product flow, technical flows, and (where applicable) a catalog of the LLM prompts involved with exact file:line pointers.
| Feature | Doc |
|---|---|
Live Notes — single live: frontmatter block (one objective + optional cron / windows / eventMatchCriteria) that turns a note into a self-updating artifact, panel UI, Copilot skill, prompts catalog |
apps/x/LIVE_NOTE.md |
| Calls (video mode) — one hands-free call engine with four presets (voice / video / share screen / practice coaching), device-derived surfaces (full-screen ⇄ floating popout), frame pipeline, prompts catalog | apps/x/VIDEO_MODE.md |
| Analytics — PostHog event catalog, person properties, use-case taxonomy, how to add a new event | apps/x/ANALYTICS.md |
Spaces — wire contract (@rowboat/spaces-protocol), stub Harbor server, golden merge fixtures; apps/x consumes via link: deps (zod versions must match exactly across the two workspaces) |
apps/harbor/CONTRACT.md |
Turn/session runtime — event-sourced storage, reference model, the npm run inspect debugger |
apps/x/packages/core/docs/turn-runtime-design.md, session-design.md |
Common Tasks
LLM configuration
- Config file:
~/.rowboat/config/models.json(v2; v1 files are migrated on boot bycore/models/migrate.ts) - Schema:
{ version: 2, providers: { <id>: { flavor, apiKey?, baseURL?, … } }, assistantModel?: { provider, model, effort? }, taskModels?: { knowledgeGraph?, meetingNotes?, liveNoteAgent?, autoPermissionDecision?, chatTitle?, backgroundTask?, subagent? }, deferBackgroundTasks? } - Providers carry credentials only (no model fields) — model lists are always fetched live via the unified catalog (
core/models/catalog.ts,models:listIPC). Model choices live inassistantModel(the one primary) andtaskModels(optional overrides that otherwise inherit the assistant). Every choice is a{ provider, model, effort? }pair —effortis the reasoning effort picked with the model (low/medium/high; missing,null, or"auto"all mean Auto = provider default). - Models catalog cache:
~/.rowboat/config/models.dev.json(OpenAI/Anthropic/Google only)
Add a new shared type
- Edit
apps/x/packages/shared/src/ - Run
cd apps/x && npm run depsto rebuild
Modify main process
- Edit
apps/x/apps/main/src/ - Restart dev server (main doesn't hot-reload)
Modify renderer (React UI)
- Edit
apps/x/apps/renderer/src/ - Changes hot-reload automatically in dev mode
Add a new dependency to main
cd apps/x/apps/main && pnpm add <package>- Import in source - esbuild will bundle it
Verify compilation
cd apps/x && npm run deps && npm run lint
cd apps/x && npm run typecheck # dev tsconfigs — the only gate that typechecks *.test.ts
Tech Stack
| Layer | Technology |
|---|---|
| Desktop | Electron 39.x |
| UI | React 19, Vite 7 |
| Styling | TailwindCSS, Radix UI |
| State | React hooks |
| AI | Vercel AI SDK, OpenAI/Anthropic/Google/OpenRouter providers, Vercel AI Gateway, Ollama, models.dev catalog |
| IPC | Electron contextBridge |
| Build | TypeScript 5.9, esbuild, Electron Forge |
Environment Variables (for packaging)
For production builds with code signing:
APPLE_ID- Apple Developer IDAPPLE_PASSWORD- App-specific passwordAPPLE_TEAM_ID- Team ID
Not required for local development.