Infinite canvas with real terminals, browser nodes, drawing tools, and AI agent integration.
OhCanvas is an open-source desktop app — an infinite whiteboard where you spawn PTY-backed terminals, browse the web inside canvas nodes, draw arrows and freehand marks, take notes, and control everything from a CLI agent. Built with Tauri 2 + React + Konva.
# Prerequisites: Node 20+, pnpm 9+, Rust 1.77+, Xcode CLI Tools (macOS)
git clone https://github.com/AJSubrizi/OhCanvas.git
cd OhCanvas
pnpm install
# Development (sidecar + Tauri window)
pnpm dev
# Web-only frontend (no Tauri window)
pnpm dev:web
# Production build
pnpm tauri buildPi CLI: to use Pi, run
pionce in your terminal to authenticate.
ohcanvas/
├── src/ # React + TypeScript frontend
│ ├── App.tsx # Root component
│ ├── main.tsx # Entry point
│ ├── styles.css # Global styles (dark theme, glassmorphism)
│ ├── state/store.ts # Zustand store (all global state)
│ ├── canvas/
│ │ ├── Canvas.tsx # Konva Stage + board-level drawing logic
│ │ ├── nodes.ts # Node spawning + auto-tiling
│ │ ├── types.ts # CanvasNode interfaces
│ │ ├── annotations/
│ │ │ ├── AnnotationOverlay.tsx # SVG overlay for browser annotations
│ │ │ └── useAnnotations.ts # Annotation state hook
│ │ └── nodes/
│ │ ├── BrowserNode.tsx # Web browser node
│ │ ├── TerminalNode.tsx # xterm terminal
│ │ ├── ShellNode.tsx # Shell terminal
│ │ ├── NoteNode.tsx # Yellow sticky note
│ │ ├── TextNode.tsx # Large text block
│ │ └── ShapeNode.tsx # Rectangle / ellipse
│ ├── ui/
│ │ ├── Navbar.tsx # Toolbar + workspace switcher
│ │ ├── CommandBar.tsx # Command bar (text + voice)
│ │ ├── PreviewDock.tsx # Live preview panel + screenshot send-to-terminal
│ │ ├── backgrounds.tsx # Animated backgrounds
│ │ ├── themes.ts # Color themes
│ │ ├── SpotifyPlayer.tsx # Spotify/YouTube/Apple Music embed player
│ │ └── voice.ts # Whisper voice recognition
│ └── bridge/
│ ├── protocol.ts # WebSocket message types
│ └── sidecar.ts # WebSocket sidecar client
│
├── sidecar/ # Node.js backend (PTY + agent runner)
│ └── src/
│ ├── index.ts # WebSocket server
│ ├── terminals.ts # PTY lifecycle management
│ └── ...
│
├── src-tauri/ # Tauri shell (Rust)
│ ├── main.rs
│ ├── Info.plist # macOS microphone privacy description
│ ├── binaries/ # Built sidecar binaries for Tauri externalBin
│ ├── tauri-plugin-llm/ # Custom LLM plugin (SmolLM2)
│ └── tauri.conf.json
│
├── public/backgrounds/ # Animated background videos
└── docs/ # Demo scripts
| Action | How |
|---|---|
| Add a terminal | Click + CLI in the navbar, pick an agent |
| Set project folder | Click Project in the navbar; terminals in that workspace reuse it |
| Move / resize | Drag a node, resize from its corners |
| Add a browser | Click the 🌐 button in the navbar |
| Annotate a web page | Click ✎ on the browser node, pick a tool (T / ↗ / ✎) |
| Draw on the canvas | Select pen or arrow in the navbar, drag to draw |
| Erase drawings | Select eraser in the navbar, brush over strokes |
| Take notes | 📝 (note) and T (text) buttons |
| Switch workspaces | Click the numbered squares left of the navbar |
| Close workspaces | Hover workspace 2+ and click × |
| Quick command | Type / in the command bar |
| Voice | Click the 🎤 mic button in the command bar |
Each workspace now owns one project folder. The first terminal in a blank workspace asks for that folder, and later terminals reuse it. Enable Multi-folder on same project in Settings only when every new CLI should ask for a different folder. The media player is global, so playback continues while switching workspaces. Settings also supports a per-workspace remote server URL that can be opened in the preview dock.
An AI agent can manipulate the canvas by printing JSON commands to stdout:
OHCANVAS {"action":"open_browser","url":"http://localhost:3000"}
OHCANVAS {"action":"open_preview","url":"http://localhost:3000"}
OHCANVAS {"action":"run_shell","command":"pnpm dev"}
OHCANVAS {"action":"send_terminal","name":"Claude","input":"run the failing tests"}
OHCANVAS {"action":"broadcast_terminal","input":"check the current browser issue"}
OHCANVAS {"action":"spawn_agent","agentType":"codex","name":"Reviewer","task":"review this change","cwd":"/path/to/project"}
OHCANVAS {"action":"tile_windows"}
OHCANVAS {"action":"close_browsers"}
OHCANVAS {"action":"add_note","text":"Idea to explore"}
OHCANVAS {"action":"kill_terminal","terminalId":"term_abc123"}
OHCANVAS {"action":"focus_terminal","terminalId":"term_abc123"}
The bottom command bar can now hand orchestration to a real CLI, for example:
chiedi a codex di coordinare claude e pi per sistemare il browser. The selected
CLI receives a compact control prompt and can delegate work to the other live
terminals by emitting OHCANVAS lines.
Useful command bar examples:
manda a tutti: controllate il browser e proponete una fix
chiudi tutti i terminali tranne codex
apri localhost:5173 nel browser
apri preview localhost:5173
sistema le finestre
| Layer | Technology |
|---|---|
| Frontend | React 18, TypeScript, Vite 6, Konva / react-konva, Zustand |
| Terminals | xterm.js + @lydell/node-pty |
| Desktop | Tauri 2 (Rust) |
| Backend | Node.js sidecar (WebSocket) |
| Voice | Whisper (native Tauri plugin) |
| Local LLM | SmolLM2 (custom Tauri plugin) |
| Persistence | Tauri file-backed state in <app_data_dir>/state with localStorage fallback |
Initial working release. Upcoming roadmap:
- Real PTY terminals (Pi, Claude Code, Codex, Cursor, Hermes, shell)
- Browser, note, text, shape nodes
- Board-level drawing (pen, arrow, eraser)
- Browser annotations (pen, arrow, text)
- Live preview with dev server detection
- Whisper voice commands
- Multiple workspaces
- Media player (Spotify, YouTube Music, Apple Music)
- Canvas control from CLI agent
- Annotation selection and editing (post-commit) — select tool, delete via
Del/Backspace/toolbar button, recolor, double-click text to edit - Performance with 10+ terminals — RAF-coalesced auto-tile, xterm scrollback 8000→3000, offscreen xterm-write culling via canvas viewport rect
- File-based persistence (instead of localStorage) —
<app_data_dir>/state/<key>.jsonvia Tauri commands with atomic writes + localStorage migration on first read - Linux x86_64 build in CI (Tauri 2 webview deps + cmake/clang for llama.cpp + whisper.cpp)
- Sidecar bundled as Tauri binary — Rust spawns
<resource_dir>/ohcanvas-sidecar[.exe](falls back topnpm --filter sidecar startin dev); CI builds it per platform viabun build --compileand installs the matching@lydell/node-pty-*prebuild package for each target. - Auto-update artifacts in release builds (
latest-release.jsongenerated from uploaded bundles) - macOS sidecar signing/notarization (Gatekeeper)
MIT