Files
helder/CLAUDE.md
2026-06-16 06:18:42 +02:00

12 KiB
Raw Blame History

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-templatingphp 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, chokidar watch (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.

  • Gitsimple-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-launches claude; 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 a postinstall) rebuilds it for Electron; it's N-API so the binary is portable.

  • Config.helder/ per project (src/main/config.ts): config.default.json regenerated on launch (full defaults / live docs), sparse config.json deep-merged over it, and theme.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 from theme.css. ai command/autoLaunch + shell flow from config into the PTYs; a .helder file watcher hot-reloads config/theme.

  • Search — ripgrep (@vscode/ripgrep, bundled binary) for content (--json, fixed-string smart-case) and the file-name list (--files), via src/main/search-service.ts. SearchModal calls it debounced and falls back to the in-memory index when window.helder is 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/updated modes are a writable buffer: a transparent textarea over a Prism-highlighted <pre> with a scroll-synced gutter (CodeEditor in editor.tsx). ⌘S saves to disk (fs:write), editor.autoSave debounce-saves on change, tabs show the dirty dot, and the git-row context menu has Discard changes gated by git.confirmDiscard. Original/Diff/Split stay read-only review views.

README steps 17 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.

  • Electron (latest stable), main + renderer + preload bridge with contextIsolation: true.
  • Renderer: React 18 + TypeScript + Vite (electron-vite scaffold). 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 top App component; in the real app, lift FS/git/terminal state into main and stream over IPC.
  • Prism PHP load order: prism-php requires prism-markup-templating to be loaded first, or every Prism.highlight call throws and silently falls back to plain text.
  • Preload must be CommonJS index.cjs and main must load ../preload/index.cjs (see electron.vite.config.ts preload rollupOptions.output). If they mismatch (or you let it build as .mjs), Electron silently loads no preload, window.helder is undefined, and the renderer silently falls back to mock data + the "terminal not available" message. Keep the filename and the main path in sync.
  • Pass on to Agent uses bracketed paste. Write inserts to the agent PTY wrapped in \x1b[200~ … \x1b[201~ so the claude CLI 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() in design/src/data.js); production should prefer real git diff output 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 claude CLI (ai.command, default claude, auto-launched when ai.autoLaunch is on). The bottom pane is a normal shell PTY. The prototype's simulated agent session (agentSeed, runAgent, bootAgent in terminals.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.json if present, else config.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)

  1. Electron shell + frameless dark window; port tokens to CSS vars; bundle JetBrains Mono.
  2. Static layout: four resizable columns + title/status bars.
  3. Real file tree + open files into tabs (read-only) with Prism highlighting.
  4. Git panel from git status (read-only) → staging + commit → the four diff modes + Split.
  5. Search (ripgrep + fuzzy).
  6. Terminals via node-pty + xterm.js; run claude in the agent pane.
  7. 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 into out/ (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 typechecktsc --noEmit over 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 in test/ (pure logic + node-side services: diff, fuzzy, highlight, config, fs, git). npm run test:watch for watch mode.
  • npm run lint — ESLint (flat config in eslint.config.js). .prettierrc.json defines formatting (not auto-applied).
  • npm run pack — unpacked app into dist/ (electron-builder, unsigned). npm run dist / dist:mac for distributables. App icon comes from build/icon.png. Native node-pty + rg are asar-unpacked so they load when packaged.

Keep all five green (typecheck · lint · test · build, and pack when touching main/packaging) when changing code.