# The Cockpit That Drives AI

## Development

- **Dev Server Port**: 3456 (run `npm run dev`)
- **Tech Stack**: Next.js 16, React, TypeScript, TailwindCSS
- **Isolating an instance**: `COCKPIT_HOME=~/.cockpit-dev npm run dev` points
  the whole data dir (config, projects, history) somewhere else — use it rather
  than testing against a live `~/.cockpit`. Note that only ONE dev server can
  run per repo checkout (Next holds a lock on `.next/`), so `PORT` alone does
  not buy a second instance; a running prod `cockpit` also refuses a second
  boot unless isolated this way.

## UI Layout

- **Three-panel swipe mode**: Uses `SwipeableViewContainer` (translateX) to place three panels side by side, with left/right swipe to switch:
  - Panel 1 **Agent** (Chat)
  - Panel 2 **Explorer** (File browser)
  - Panel 3 **Console** (Terminal + browser bubbles)
- **All three panels are always rendered simultaneously**; switching panels is just a CSS transform translation — components are never unmounted/remounted
- **UI component considerations**: When building menus, modals, and floating popovers, be mindful of the three-panel layout:
  - Positioning calculations must account for the boundaries of the current panel
  - z-index levels must be managed consistently
  - Prevent components from overflowing into adjacent panels

## React Performance Conventions

All three panels + every open chat tab stay mounted, so one state change (session
switch, WS push, terminal tick) re-renders large sibling subtrees. Props to a
`memo`'d heavy renderer (chat `MessageBubble`, Explorer previews, Console bubbles)
must be **referentially stable** — one unstable prop silently defeats the `memo`:

- Callbacks → `useCallback`; if a dep churns every render (`fileTree`, live
  `sessionId`), use a ref indirection so the passed-down identity never changes.
- Objects/arrays (e.g. `extra={{…}}`) → `useMemo`. Inside `.map()` extract a
  `memo`'d row (see `ConsoleBubbleRow`) fed stable callbacks + per-item booleans.
- Expensive per-render work → `useMemo([content])`; never parse/format an
  O(document) blob inline in JSX.

## Effect Conventions

All IO / side-effects / dependencies / errors go through the
`Effect<A, E, R>` paradigm. See **`EFFECT.md`** for the full contract:
- Tagged Error types (DBError / WSError / FSError / AgentError / ...)
- Service Tag + Layer.scoped templates for connection pools / subprocesses
- API route / WebSocket handler templates
- React bridging (`useEffectQuery` / `BrowserRuntime.runPromise`)
- `Effect.withSpan` / `CockpitConfig` / Logger conventions
- Server vs Browser bundle boundary rules

When adding any new IO operation (HTTP route, DB query, WS handler,
client-side fetch), **first match a template in EFFECT.md §3-§7**; do not
write raw `fetch` / `try-catch` / `setInterval` in business code.

## Project Structure

Business code lives in `packages/`; `src/` is intentionally minimal
framework boot. See `MODULES.md` for the dependency rules.

- `/src/app/` - Next.js routing only (page.tsx + layout.tsx + one-line
  `route.ts` shims that re-export from feature packages)
- `/src/lib/` - Server bootstrap: `wsServer.ts` (WS server),
  `fileWatcher.ts` (fs watcher)
- `/packages/feature/` - Self-contained domain features:
  - `agent/` - Chat domain (Claude/Ollama/Codex/Kimi/DeepSeek/GLM), scheduled
    tasks, slash commands, sidebar panels, tool-call snapshots (shadow git
    per project under `~/.cockpit/snapshots/`, per-call diff viewer)
  - `comments/` - Code annotation API + hooks
  - `console/` - Terminal + browser bubbles + DB bubbles (Postgres / MySQL
    / Redis / Neo4j / MongoDB / Bash)
  - `explorer/` - File browser + code rendering (DiffView, CodeViewer,
    InteractiveMarkdownPreview, PreviewModal) + git + LSP
  - `skills/` - SKILL.md parser + slash autocomplete + cross-frame bus
  - `workspace/` - Application integrator (Workspace, TabManager,
    Providers, SettingsModal, NoteModal, SessionBrowser)
- `/packages/shared/` - Cross-feature infrastructure:
  - `i18n/` - Translation dictionary + i18next singleton
  - `ui/` - UI primitives (Toast, MarkdownRenderer, Tooltip,
    codeHighlighter, Swipeable*, useViMode, useWebSocket, …)
  - `utils/` - Pure utilities (paths, ollamaEnv, platform, shortId)
- `/apps/` - Built-in HTML apps (`file-viewer/`). Deliberately NOT under
  `public/` — they are served by a route so the bash SDK can be injected
  (see **HTML Apps Runtime**)
- `/public/html-lib/` - Same-origin hosted frontend libs for HTML apps
  (React, ReactDOM, Babel, `theme.css`, and the standalone
  markdown / json / csv / pdf widget bundles). Regenerated by
  `scripts/build-html-lib.mjs` on every `predev` / `prebuild`
- `/skills/` - Builtin slash-command prompts, one `SKILL.md` per command
  (`/qa`, `/cr`, …). English only, **no TypeScript involved**: the directory
  set IS the registry — both dispatch (`slashCommands.ts`) and the
  autocomplete route read it, so adding a command is `mkdir skills/<cmd>` +
  one file. Shipped in `package.json#files`, so anything added here lands in
  the npm package. Project-internal playbooks go in `/docs/skills/` instead.
- `/chrome-extension/` - Chrome extension (Manifest V3, independent
  sub-project)
- `/bin/` - CLI entry points (`cockpit.mjs`, `postinstall.mjs`)

## HTML Apps Runtime

Two address spaces, one route
(`packages/feature/explorer/src/server/api/apps.ts`):

- `/apps/builtin/<name>/index.html` — apps shipped in `/apps/`
- `/apps/local/<abs path>` — any file on this machine; this is also the
  plain local-file server (relative sub-resources, images, css)

**SDK injection has no marker**: every `.html` served by this route gets
`window.cockpit`. Nothing to opt into, nothing to forget. `cockpit-name` is
purely a registry concern (panel card + `/name`) and has no bearing on what
runs.

**Do not add a marker back.** Two earlier designs gated injection on one (a URL
flag, then a meta tag) and both failed silently — the header comment in
`apps.ts` records how, so the mistake isn't repeated.

Making it unconditional is free because **injection was never a security
boundary**: any page served here is same-origin and can open its own WebSocket
to `/ws/bash` regardless. **The real gate is the same-origin check on that
upgrade** (`src/lib/wsServer.ts`), which means the security decision is *what
you choose to preview*, not what we inject.

Say the consequence out loud when working here: **previewing any local `.html`
runs it**, with full shell access. The iframe carries no `sandbox` attribute
(matching the console browser bubble), so a previewed page can also navigate
the whole Cockpit window away. Surfaces that preview unreviewed content — e.g.
a `.html` straight out of a git diff — are making that call for the user.

When working in this area:

- **Built-in apps must only use capabilities user apps have** —
  `window.cockpit.bash`, `/html-lib/*`, and the `cockpit-*` meta tags. No
  privileged endpoints, no internal postMessage protocols. They double as the
  reference implementations for the `/html` skill, so a shortcut taken here
  becomes advice users cannot follow.
- **Root-relative paths 404** except `/html-lib/*` and `/apps/*`, which the
  server hosts. Same-level relative refs (`./app.jsx`) and CDN URLs resolve
  normally.
- **Markdown images**: rewrite relative srcs to `/apps/local/<abs>`, never to
  `data:` URLs — react-markdown's `defaultUrlTransform` drops every protocol
  outside http/https/mailto/xmpp, silently leaving the `<img>` with no `src`
  at all, in both the `![](…)` and raw-HTML forms.
- **Never hand-assemble these URLs.** The builders and their reverse
  (`toLocalAppUrl` / `toFileViewerUrl` / `fromLocalAppUrl`, plus the per-segment
  encoding Windows paths depend on) live in
  `packages/shared/utils/src/htmlBashSdk.ts`.
- Directory URLs hit Next's trailingSlash redirect, after which the shell's
  relative `./app.jsx` fetch resolves one level too high and 404s — always
  spell out `index.html`.

## Key Features

- File browser with virtual scrolling and syntax highlighting (Shiki)
- Git status and history integration
- Per-tool-call project snapshots (shadow git per cwd, one commit per
  mutating tool call, 7-day retention; message FileDiff icon opens the
  per-call diff viewer)
- Git blame view
- Code search with Cmd+F (case sensitive / whole word matching)
- ESC key exits blame view first, then closes modal (3s debounce)

## Commands

```bash
npm run dev      # Start dev server on port 3456
npm run build    # Build for production
npm run setup    # Build + npm link
npm run lint     # Run ESLint
cockpit          # Start production server on port 3457 (prefer this — primary entry)
cockpit-dev      # Start dev server on port 3456 (dev only; no short alias)
cockpit -v       # Show version
```

## npm Publish

Publishing is done via GitHub Actions — **never publish locally with `npm publish`**.

```bash
npm version patch          # Bump version (patch/minor/major)
git push origin main       # Push commit
git push origin v<version> # Push tag → triggers CI publish
```

The `v*` tag push triggers `.github/workflows/publish.yml` which:
1. Builds the project
2. Publishes to npm (`@surething/cockpit`) with provenance
3. Creates a GitHub Release with auto-generated notes

## Project Characteristics

- **Purely local application**: All API request latency is under 10ms
- **No API caching needed**: Local requests are fast enough that caching provides negligible performance gains while introducing data consistency issues

## Claude Code Usage Guidelines

- **Browser testing**: Use `evaluate_script` for DOM manipulation; avoid `take_screenshot` (consumes excessive tokens)
- **Minimize screenshots**: Only take screenshots when visual confirmation is truly needed
- **MCP tools**: Do not use MCP tools unless the user explicitly requests it (e.g., "use xxx")
- **Git commits**: Do not auto-commit code; only commit when the user explicitly says "commit"
- **English everywhere in code**: Write commit messages (subject and body), code comments, and CLI/console log output entirely in English; Chinese may appear only in quoted literals (e.g. UI copy, example prompts). Conversation replies stay in Chinese

<!-- cdd:begin -->
## cdd (Constraint-Driven Development)

- Constraints live in `constraints/`. Changing one is a constraint change and needs human review; never weaken a constraint to make a check pass.
- cdd config lives in `.cdd/` (hidden directory): `config.json` and `baseline.json` decide how changes are judged. Editing them is a constraint change too — do not edit them to get a pass.
- Before committing, run `cdd change` to classify the change as A/B/C/D. A type-D change (incident fix) must add a new constraint that fails before the fix.
- Where checks run: local self-check (default) · commit hook · CI — same command in each. Which ones this repo actually wires up is defined by its own hook / CI config.
<!-- cdd:end -->
