# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview Triple-C (Claude-Code-Container) is a Tauri v2 desktop application that sandboxes Claude Code inside Docker containers. It has two main parts: a React/TypeScript frontend, a Rust backend, and a Docker container image definition. ## Build & Development Commands All frontend/tauri commands run from the `app/` directory: ```bash cd app npm ci # Install dependencies (required first time) npx tauri dev # Launch app in dev mode with hot reload (Vite on port 1420) npx tauri build # Production build (outputs to src-tauri/target/release/bundle/) npm run build # Frontend-only build (tsc + vite) npm run test # Run Vitest once npm run test:watch # Run Vitest in watch mode ``` Rust backend is compiled automatically by `tauri dev`/`tauri build`. To check Rust independently: ```bash cd app/src-tauri cargo check # Type-check without full build cargo build # Build Rust backend only ``` Container image: ```bash docker build -t triple-c-sandbox ./container ``` ### Linux Build Dependencies (Ubuntu/Debian) ```bash sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev libsoup-3.0-dev patchelf libssl-dev pkg-config build-essential ``` ## Architecture ### Two-Process Model (Tauri IPC) - **React frontend** (`app/src/`) renders UI in the OS webview - **Rust backend** (`app/src-tauri/src/`) handles Docker API, credential storage, and terminal I/O - Communication uses two patterns: - `invoke()` — request/response for discrete operations (CRUD, start/stop containers) - `emit()`/`listen()` — event streaming for continuous data (terminal I/O) ### Terminal I/O Flow ``` User keystroke → xterm.js onData() → invoke("terminal_input") → mpsc channel → docker exec stdin docker exec stdout → tokio task → emit("terminal-output-{sessionId}") → listen() → xterm.js write() ``` ### Frontend Structure (`app/src/`) - **`store/appState.ts`** — Single Zustand store for all app state (projects, sessions, UI). The main area is a single ordered tab strip holding two tab kinds, keyed `term:` and `home:`; `activeSessionId` is *derived* from `activeTabKey` so exactly one thing is current. `tabOrder` is user-reorderable (drag, or `Ctrl+Shift+←/→` via `moveActiveTab`) — so **never treat a tab's position as identity**: address tabs by key, and index only through `tabOrder`. `moveTab` deliberately does not activate what it moves. - **The tab drag is pointer events, not HTML5 drag-and-drop, and must stay that way.** Tauri's `dragDropEnabled` blocks HTML5 drag inside the webview on Windows, and it cannot simply be turned off: `TerminalView` needs Tauri's native drag-drop event because it is the only one that carries dropped *file paths*. An HTML5 drag also carries a `DataTransfer`, which the default handler types into any text field the drag is released over. - **A new app-level shortcut must not swallow a text-editing chord.** `useKeyboardShortcuts` binds on `document` in the capture phase, so `inTextField()` guards the arrow bindings — excluding xterm's helper textarea, which is an input-method shim rather than a field. - **`hooks/`** — All Tauri IPC calls are encapsulated in hooks (`useTerminal`, `useProjects`, `useDocker`, `useSettings`) - **`lib/tauri-commands.ts`** — Typed `invoke()` wrappers; TypeScript types in `lib/types.ts` must match Rust models - **`components/terminal/TerminalView.tsx`** — xterm.js integration with WebGL rendering, URL detection for OAuth flow - **`viewer/`** — the terminal file viewer's window (second Vite entry `viewer.html` → `src/viewer/main.tsx`; CodeMirror 6). `lib/filePathLinks.ts` decides what a path is; `components/terminal/filePathLinkProvider.ts` registers it with xterm. The OSC 8 handler now runs with `allowNonHttpProtocols` on and dispatches `file:` to the viewer, so every other scheme must be refused *there*. `viewer.html` must never carry an inline `