12 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project state
Phase 1 is scaffolded. An electron-vite + React 18 + TypeScript app now lives at the repo root (src/main, src/preload, src/renderer). The prototype has been ported faithfully and renders against the mock data — full UI, git panel, four diff modes + Split, search, and the simulated terminals are all working. JetBrains Mono is bundled locally via @fontsource/jetbrains-mono; Prism is wired with the correct markup-templating→php load order; the renderer↔main clipboard bridge is in place (src/preload/index.ts).
Phase 2 (real integrations) — complete; DESIGN.md fully implemented. The app is feature-complete against the functional spec (the only intentional exception is the separate Go-to-File overlay — ⌘P aliases the unified search instead, per the resolved decision). Editor is writable (save · autosave · dirty tabs · discard); session restore brings back open tabs/active/view modes; close-dirty prompts Save/Don't-Save/Cancel. There's a vitest suite (npm test, 51 tests) + ESLint + electron-builder packaging. All over IPC through the preload bridge (src/preload/index.ts):
-
Filesystem — tree + in-memory content index,
chokidarwatch (src/main/fs-service.ts). The tree/index/search share one source of truth:rg --files(honors gitignore +files.exclude, includes dotfiles), with a recursive-walk fallback when ripgrep is unavailable. -
Git —
simple-git: status→A/M/D/R, the four diff views from HEAD-vs-worktree pairs, stage/unstage/commit/discard (src/main/git-service.ts). -
Terminals — real PTYs via
node-pty(src/main/pty-service.ts) rendered with@xterm/xterm(src/renderer/src/terminals.tsx). Agent pane is a shell that auto-launchesclaude; bottom pane is a plain shell. Pass-on-to-Agent writes bracketed paste (\x1b[200~ … \x1b[201~) to the agent PTY. node-pty is native —npm run rebuild(also apostinstall) rebuilds it for Electron; it's N-API so the binary is portable. -
Config —
.helder/per project (src/main/config.ts):config.default.jsonregenerated on launch (full defaults / live docs), sparseconfig.jsondeep-merged over it, andtheme.css(created once, never overwritten) injected over the built-in dark theme. The code font + size are CSS vars (--code-font/--code-size/--term-size) the editor + xterm read, overridable fromtheme.css. ai command/autoLaunch + shell flow from config into the PTYs; a.helderfile watcher hot-reloads config/theme. -
Search — ripgrep (
@vscode/ripgrep, bundled binary) for content (--json, fixed-string smart-case) and the file-name list (--files), viasrc/main/search-service.ts.SearchModalcalls it debounced and falls back to the in-memory index whenwindow.helderis absent.
The renderer consumes FS/git/config/search via the store in src/renderer/src/project.tsx (useProject / useProjectActions), which falls back to the mock + default config when window.helder is absent (browser preview). src/main/index.ts registers all IPC handlers + the debounced watchers.
- Editing — the
code/updatedmodes are a writable buffer: a transparent textarea over a Prism-highlighted<pre>with a scroll-synced gutter (CodeEditorineditor.tsx).⌘Ssaves to disk (fs:write),editor.autoSavedebounce-saves on change, tabs show the dirty dot, and the git-row context menu has Discard changes gated bygit.confirmDiscard. Original/Diff/Split stay read-only review views.
README steps 1–7 plus config + editing are all implemented for real. The editable overlay keeps the caret in view (the textarea is overflow-hidden under the scroller, so CodeEditor scrolls the container on input/keyup/click).
Not yet done: packaging (electron-builder → .app/.dmg) — out/ is dev build output only, there is no distributable yet.
The two handoff documents remain the contract:
DESIGN.md— functional/UX source of truth. Every panel, interaction, state, and edge case at the behavior level. Read this for what the app does.design_handoff_helder_workbench/README.md— technical source of truth. Structure, design tokens, recommended stack, real-integration mechanics, and the suggested implementation order. Read this for how to build it.
The prototype in design_handoff_helder_workbench/design/ (React 18 + Babel from CDN, all mock data) is a visual/interaction reference only — do not ship it as-is. The HTML is canonical for look and feel; design/styles.css's :root block is the canonical design-token list. The design/src/*.jsx files map directly to the components to build, but their mock data (data.js) and simulated terminals/agent must be replaced with real integrations.
What Helder is
A dark-only (no light mode, no theme toggle) Electron desktop code workbench for reviewing code written by an AI agent. One project per window. Four resizable columns left→right: Source Control (git), Explorer (file tree), Editor (tabs + diff), Right column (Claude agent terminal stacked over a shell terminal). Plus a top title bar and bottom status bar. The defining feature is the Copy reference / Pass on to Agent flow that pushes path:line references into the agent's input.
Recommended stack (no codebase exists — follow README)
- Electron (latest stable), main + renderer + preload bridge with
contextIsolation: true. - Renderer: React 18 + TypeScript + Vite (
electron-vitescaffold). Prototype is already React, so component structure ports directly. - Syntax highlighting: Prism 1.29 (or swap to Shiki/CodeMirror 6; token→color mapping is documented in README).
- Fonts: UI = system stack; code/mono = JetBrains Mono bundled locally (never Google Fonts CDN in Electron).
Architecture rules and gotchas (these will bite if ignored)
- Renderer never touches the filesystem, git, or PTYs directly. All FS (
fs+chokidar), git (git/simple-git), search (rg+ fuzzy), terminals (node-pty+xterm.js), and clipboard go through the main process via IPC / the preload bridge. The prototype keeps all state in the topAppcomponent; in the real app, lift FS/git/terminal state into main and stream over IPC. - Prism PHP load order:
prism-phprequiresprism-markup-templatingto be loaded first, or everyPrism.highlightcall throws and silently falls back to plain text. - Preload must be CommonJS
index.cjsandmainmust load../preload/index.cjs(seeelectron.vite.config.tspreloadrollupOptions.output). If they mismatch (or you let it build as.mjs), Electron silently loads no preload,window.helderis undefined, and the renderer silently falls back to mock data + the "terminal not available" message. Keep the filename and themainpath in sync. - Pass on to Agent uses bracketed paste. Write inserts to the agent PTY wrapped in
\x1b[200~ … \x1b[201~so theclaudeCLI treats it as pasted, unsubmitted input. Insert must never submit — it lands as a new line so the user can stack several references before sending. - The four diff view modes (Original / Updated / Diff / Split) all derive from one original-text + updated-text pair per changed file. The prototype computes this with an LCS line diff (
buildDiff()indesign/src/data.js); production should prefer realgit diffoutput but keep the same four derived views and the same color language everywhere: red = removed/changed-from, green = added/changed-to, syntax highlighting on in all modes. - The agent pane is just a terminal running the
claudeCLI (ai.command, defaultclaude, auto-launched whenai.autoLaunchis on). The bottom pane is a normal shell PTY. The prototype's simulated agent session (agentSeed,runAgent,bootAgentinterminals.jsx) exists only to show the visual style — keep the styling, drop the fakery. - Chrome budget: title bar + tab strip + panel headers + status bar combined should stay ≈10% of vertical height. Keep it minimal.
Confirmed decisions (the "Open assumptions" in DESIGN.md are resolved — do not re-ask)
- Search overlay layout: content matches left (70%), file-name matches right (30%) — keep as designed.
- Tabs show an unsaved indicator (a dot in place of the close control) because auto-save defaults off (
editor.autoSave). - Explorer right-click offers a file-level Copy reference (project-relative path only), consistent with the editor's Copy reference — in scope.
Scope boundaries (this version)
- Git covers staging, unstaging, committing, and discarding only. Push, pull, fetch, and branch switching are explicitly out of scope. The branch summary bar and status bar are display-only.
- One agent terminal and one shell terminal — no additional tabs or sessions.
- The breadcrumb and status-bar items are display only (not clickable, do not navigate).
- Discard is the only destructive git action and must confirm first (
git.confirmDiscard, default on). Staging/unstaging do not confirm by default.
Configuration
Settings are project-scoped, living in a .helder/ folder in the opened project's root:
.helder/config.json— sparse; only user-overridden values..helder/config.default.json— full defaults, regenerated on launch from built-in defaults (live documentation of every setting; the app never reads user edits from it).- Effective value =
config.jsonif present, elseconfig.default.json, merged key by key. .helder/theme.css— custom CSS theme applied over the built-in dark theme; code font and font size live here, not in the config files.
Design tokens
Canonical source is the :root block in design_handoff_helder_workbench/design/styles.css. Surfaces are cool charcoal (--bg-0 editor #16171a → --bg-3 headers/tabs #23262b); single cool-blue accent --accent #4d8dff; git status --add #5cbd6b / --del #e0696a / --mod #d8a85c / --ren #5aa6d6. File-type icons are 15×15 monogram chips (no brand logos). Recreate UI icons as a small inline-SVG set (or Lucide), keeping the monogram chips for file types. Respect prefers-reduced-motion; keep motion subtle.
Suggested implementation order (from README)
- Electron shell + frameless dark window; port tokens to CSS vars; bundle JetBrains Mono.
- Static layout: four resizable columns + title/status bars.
- Real file tree + open files into tabs (read-only) with Prism highlighting.
- Git panel from
git status(read-only) → staging + commit → the four diff modes + Split. - Search (ripgrep + fuzzy).
- Terminals via node-pty + xterm.js; run
claudein the agent pane. - Copy reference + Pass-on-to-Agent (clipboard + bracketed-paste into the agent PTY).
Commands
npm run dev— launch the app in Electron with HMR (electron-vite dev).npm run build— type-stripped production build intoout/(electron-vite build). A frontend change is not done until this succeeds.npm run preview/npm start— run the built app (electron-vite preview).npm run typecheck—tsc --noEmitover the renderer (tsconfig.web.json) and main/preload (tsconfig.node.json). The build itself uses esbuild and does NOT type-check, so run this separately to catch type errors.npm test— vitest suite intest/(pure logic + node-side services: diff, fuzzy, highlight, config, fs, git).npm run test:watchfor watch mode.npm run lint— ESLint (flat config ineslint.config.js)..prettierrc.jsondefines formatting (not auto-applied).npm run pack— unpacked app intodist/(electron-builder, unsigned).npm run dist/dist:macfor distributables. App icon comes frombuild/icon.png. Nativenode-pty+rgare asar-unpacked so they load when packaged.
Keep all five green (typecheck · lint · test · build, and pack when touching main/packaging) when changing code.