diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..86c0e99 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changelog + +## 2026-09-30 + +- [docs] Add the AI Working Agreement and context files (files: CLAUDE.md, CHANGELOG.md, docs/CONTEXT.md, docs/ARCHITECTURE.md, docs/DECISIONS.md, docs/TODO.md) + +## 2026-09-29 + +- [docs] Add README screenshots and restructure the README (files: README.md, docs/screenshots/) +- [release] v1.1.0 (files: package.json) +- [ci] Fix the Windows lint and Linux e2e failures; publish a GitHub release on version tags (files: .github/workflows/) +- [settings] Add appearance options (theme, accent, palette, font) and the update check (files: src/main/update-check.ts, src/renderer/components/SettingsView.tsx) +- [platform] Add Windows, Linux distro packages, tiling compositors, and shared CLAUDE.md/skills (files: src/main/platform.ts, src/main/shared-config.ts, electron-builder.yml) +- [m5] Add packaging, the README, and shutdown hardening +- [m1-m4] Add the PTY grid with accounts, layouts, and persistence diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..017d2a5 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,53 @@ +# AI Working Agreement + +## Session start (always, in order) + +1. Read `docs/CONTEXT.md` (project map + current state). Do NOT scan the repo. +2. Read the top 30 lines of `CHANGELOG.md` (recent changes). +3. Read `docs/TODO.md` only if the task is about planning or next steps. +4. Read other files ONLY when the task needs them. Open by path from the file map; do not glob/grep the whole repo unless the map has no answer. + +## Do not re-read + +- Skip any file listed under "Stable / don't reopen" in `docs/CONTEXT.md` unless the task touches it. +- Trust `docs/ARCHITECTURE.md` and `docs/DECISIONS.md`. Don't re-derive decisions already logged there. If one looks wrong, say so and propose a change; don't silently override. + +## Context files (create if missing) + +- `docs/CONTEXT.md`: one-page brief. Sections: Purpose, Stack, Run/build/test commands, Directory map (path: one-line role), Conventions, Stable/don't reopen, Known gotchas, Current focus. +- `docs/ARCHITECTURE.md`: modules, data flow, external services, env vars (names only, no secrets). +- `docs/DECISIONS.md`: append-only ADR log. Format: `YYYY-MM-DD | Decision | Why | Alternatives rejected`. +- `docs/TODO.md`: Now / Next / Later / Blocked. +- `CHANGELOG.md`: newest first. Format: `## YYYY-MM-DD` then bullets `- [area] what changed (files: a.ts, b.ts)`. Keep entries to 1 line each. + +## After every task (mandatory) + +1. Add a CHANGELOG entry: what changed, files touched. +2. Update `docs/CONTEXT.md` if: files/dirs added/removed, commands changed, a new gotcha was found, or "Current focus" changed. +3. Append to `docs/DECISIONS.md` if a non-obvious choice was made. +4. Update `docs/TODO.md` (move/close/add items). +5. Keep these files short. Compress old entries; never let CONTEXT.md exceed ~150 lines. + +## Working rules + +- Terse output. Commands/code first, minimal prose. +- Before editing: state the plan in ≤5 bullets. Then do it. +- Smallest diff that solves the problem. No unrelated refactors. +- Match existing conventions in `docs/CONTEXT.md`. +- Never invent files, APIs, or env vars. If unsure, ask or check the map. +- Run tests/lint/build commands from CONTEXT.md before declaring done; report result. +- Never commit secrets. Reference env var names only. +- If context files are stale or contradict the code, fix them and note it in CHANGELOG. + +## When context is missing + +If `docs/CONTEXT.md` doesn't exist: explore once, generate all context files above, then proceed. Never repeat this exploration in later sessions. + +## End-of-session handoff + +Finish with: + +- Done: (1–3 bullets) +- Changed files: (list) +- Next: (1–3 bullets) +- Open questions/blockers: (if any) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..4e066fc --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,61 @@ +# Wraithgrid: Architecture + +## Processes +- **Main** (`src/main`): owns the filesystem, child processes, and network. Sole writer of `config.json`. +- **Preload** (`src/preload`): exposes a fixed typed API over `contextBridge`; bundled into one file. +- **Renderer** (`src/renderer`): React UI, sandboxed, `contextIsolation` on, `nodeIntegration` off. +- **Shared** (`src/shared`): types, zod schemas, IPC channel names, pure helpers used by all three. + +## Main modules +| Module | Role | +|---|---| +| `index.ts` | App lifecycle, BrowserWindow, single-instance lock, PATH merge, composition root | +| `ipc.ts` | `registerIpc(deps)`: one handler per channel, zod-validated input | +| `pty-manager.ts` | `PtyManager`: spawn/write/resize/kill; `KillPolicy` strategy (POSIX group signal vs Windows ConPTY list) | +| `pane-env.ts` | Pane environment; `CLAUDE_CONFIG_DIR` only for claude panes | +| `config-store.ts` | `ConfigStore`: parse/migrate/sanitize, atomic write, quarantine to `config.bad-.json` | +| `claude-detect.ts` | Locate `claude` on PATH or via override; read version | +| `platform.ts` | Desktop/window-chrome detection, default shell, extra bin dirs, spawn command, login-shell PATH | +| `shared-config.ts` | Symlink/junction/hardlink shared CLAUDE.md + skills; backups, never deletes | +| `update-check.ts` | `UpdateChecker`: GitHub Releases API, cached, report only | +| `paths.ts` | Config file path, accounts root, `~` resolution, `isStrictlyInside` guard | + +## Renderer +- `main.tsx` builds `Services` (API bridge, PtyBus, store) and provides them via context. +- `app/store.ts` (zustand): config + runtime pane state, all actions; persists via `config:set`. +- `lib/pty-bus.ts`: fans out `pty:data`/`pty:exit` events to panes. +- `lib/status.ts`: derives running/idle/awaiting-approval/login from output (ANSI-stripped). +- `layout/tree.ts`: pure split-tree ops; `presets.ts`, `focus.ts` on top. +- `lib/scheduling.ts`: `StaggeredQueue` starts restored panes 150 ms apart (NFR-2). + +## Data flow +1. Launch → main loads `config.json` → renderer `config:get` → store hydrates. +2. Pane start → `pty:create {paneId, accountId, cwd, args}` → main resolves account from its own + config → `buildPaneEnv` → `PtyManager.spawn` → `pty:data`/`pty:exit` events back. +3. Keystrokes → `pty:write`; resize → `pty:resize`; close → `pty:kill` (process tree). +4. Any config change → store → `config:set` (validated) → atomic write. +5. Quit → every pane's process tree killed. + +## IPC surface +Full list in `src/shared/ipc-channels.ts`; schemas in `src/shared/ipc-contract.ts`. Groups: pty, +config, dialog, window, claude detect, account dir create/delete, openExternal (http/https only), +shared apply, app info, update check. + +## External services +- GitHub Releases API for `cachewraith/wraithgrid` (optional update check; fixed URL). +- Nothing else. No telemetry. + +## Persistence +- `config.json` in userData: `~/.config/wraithgrid/` (Linux), `%APPDATA%\wraithgrid\` (Windows). +- Account config dirs default under `~/.wraithgrid/accounts/`. Wraithgrid does not read inside them. + +## Env vars (names only) +- Read by app: `WRAITHGRID_USER_DATA_DIR` (override userData, tests), `WRAITHGRID_DEVTOOLS` + (`1` opens devtools), `ELECTRON_RENDERER_URL` (dev server), `TERM`, `PATH`, `ComSpec`. +- Set by app: `CLAUDE_CONFIG_DIR` (per claude pane), `WRAITHGRID_RESOLVING_ENVIRONMENT=1` + (during login-shell PATH probe). +- Build scripts: `WRAITHGRID_BUILD_IMAGE`, `WRAITHGRID_NODE_VERSION` (`scripts/dist-linux-docker.sh`). + +## CI/CD +- `build.yml`: push/PR → typecheck, lint, unit, e2e (Linux, xvfb), package (Ubuntu 22.04 + Windows). +- `release.yml`: `v*.*.*` tag → verify tag = package.json → build → SHA256SUMS → `gh release create`. diff --git a/docs/CONTEXT.md b/docs/CONTEXT.md new file mode 100644 index 0000000..af9ab8a --- /dev/null +++ b/docs/CONTEXT.md @@ -0,0 +1,76 @@ +# Wraithgrid: Context + +## Purpose +Electron desktop app that runs many official `claude` CLI sessions side by side, one per pane, +each with its own account (`CLAUDE_CONFIG_DIR`) and project folder. Windows + Linux. v1.1.0. + +## Stack +Electron 44, electron-vite 5, React 19, TypeScript 6, zustand, zod 4, @xterm/xterm 6, node-pty, +react-resizable-panels, @dnd-kit. Tests: Vitest (unit), Playwright (Electron e2e). pnpm 11, Node ≥22.12. + +## Commands +- `pnpm i` — install; rebuilds node-pty for Electron (allow electron/esbuild/node-pty builds) +- `pnpm dev` — run with hot reload +- `pnpm typecheck` — tsc over node + web projects +- `pnpm lint` — ESLint + Prettier check (`pnpm format` to fix) +- `pnpm test` — Vitest unit tests (`tests/unit`) +- `pnpm test:e2e` — build, then Playwright (`tests/e2e`; needs a display, CI uses xvfb-run) +- `pnpm build` — production build into `out/` +- `pnpm dist:linux:portable` — Linux packages in Ubuntu 22.04 Docker; `pnpm dist:win` on Windows +- Release: `npm version -m "chore: release %s" && git push --follow-tags` + +## Directory map +- `src/main/index.ts`: app entry; window, PATH resolution, wires ConfigStore/PtyManager/IPC +- `src/main/ipc.ts`: all IPC handlers; validates every payload with zod contracts +- `src/main/pty-manager.ts`: spawns/kills pane processes; per-OS KillPolicy +- `src/main/pane-env.ts`: builds a pane's env (sets `CLAUDE_CONFIG_DIR` for claude panes) +- `src/main/config-store.ts`: config.json load/save, atomic write, bad-file quarantine +- `src/main/claude-detect.ts`: finds the `claude` binary and version +- `src/main/platform.ts`: OS/desktop differences as pure functions (chrome, shell, PATH, spawn) +- `src/main/shared-config.ts`: link one CLAUDE.md + skills/ into every account +- `src/main/update-check.ts`: GitHub Releases check (fixed URL, report only) +- `src/main/paths.ts`: config/account paths, `isStrictlyInside` guard +- `src/preload/index.ts`: typed bridge; renderer never sees ipcRenderer +- `src/shared/ipc-channels.ts`: the complete IPC channel list +- `src/shared/ipc-contract.ts`: zod schemas for IPC args/events +- `src/shared/schema.ts`: persisted config schema, migrate/sanitize/parse +- `src/shared/types.ts`: domain types, themes, accents, palettes, fonts +- `src/shared/args.ts`, `src/shared/paths.ts`: launch-args parsing, pure path helpers +- `src/renderer/main.tsx`: composition root (builds Services) +- `src/renderer/app/store.ts`: zustand store, all app actions (largest file) +- `src/renderer/app/{App,services,shortcuts}`: root view, DI context, key matching +- `src/renderer/components/`: UI (PaneGrid, Pane, Terminal, dialogs, Settings/Accounts views) +- `src/renderer/layout/`: pure split-tree ops, presets, directional focus +- `src/renderer/lib/`: PtyBus, status detection, terminal themes, ANSI strip, scheduling +- `src/renderer/styles/`: design tokens + base CSS +- `tests/unit/`, `tests/e2e/`, `tests/fixtures/fake-claude.sh`: tests + fake CLI +- `scripts/`: Docker Linux build, multi-distro package smoke test +- `build/`: icons; `.github/workflows/`: build.yml (CI), release.yml (tag → release) +- `docs/REQUIREMENTS.md`: original requirements (FR-*/NFR-* IDs); `docs/screenshots/` + +## Conventions +- Conventional commits: `type(scope): summary`. +- Prettier + ESLint; no semicolons, single quotes (see existing files). +- `@shared` alias → `src/shared` (vite, vitest). +- Main-process logic as pure functions of `(platform, env)` / injected deps so it's unit-testable. +- Every IPC channel listed in `ipc-channels.ts` with a zod schema in `ipc-contract.ts`; main + resolves accounts from its own config, never trusts renderer paths. +- Dependencies pinned to exact versions; GitHub Actions pinned by SHA. +- Short file-top comment explaining *why* a module exists. + +## Stable / don't reopen +- `docs/REQUIREMENTS.md`, `docs/ui-prototype.dc.html`, `docs/screenshots/` +- `electron-builder.yml`, `scripts/*.sh`, `build/` +- `src/renderer/lib/{ansi,ids,scheduling}.ts`, `src/renderer/layout/presets.ts` + +## Known gotchas +- node-pty compiles against the host glibc: ship Linux builds from Ubuntu 22.04 (portable script / CI). +- Launcher-started app lacks shell PATH: main asks the login shell once (skipped when `TERM` set). +- Windows `claude.cmd` runs through cmd.exe: args with `& | < > ^ % "` are refused. +- Sandboxed preload can't require node_modules: preload is bundled (`externalizeDeps: false`). +- Single-instance lock: a second launch exits. +- Tests use `WRAITHGRID_USER_DATA_DIR` for a throwaway config dir. +- AppImage on Ubuntu 24.04+/Kali blocked by AppArmor; prefer .deb. + +## Current focus +v1.1.0 released (appearance settings, update check, Windows + distro packages). No active task. diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md new file mode 100644 index 0000000..843cfe8 --- /dev/null +++ b/docs/DECISIONS.md @@ -0,0 +1,15 @@ +# Decisions + +`YYYY-MM-DD | Decision | Why | Alternatives rejected` + +Entries dated 2026-09-29 are reconstructed from the code and commit history during the 2026-09-30 bootstrap. + +- 2026-09-29 | Run the official `claude` CLI in a PTY, one `CLAUDE_CONFIG_DIR` per account | Separate logins without touching credentials | Custom client; proxying or copying tokens +- 2026-09-29 | Electron + React + xterm.js + node-pty | Full terminal fidelity, cross-platform | Native per-OS apps; web-only terminal +- 2026-09-29 | Sandboxed renderer; fixed IPC list with zod-validated payloads; main resolves accounts itself | Renderer can't reach the FS/processes except through checked calls | Exposing ipcRenderer; trusting renderer-supplied paths +- 2026-09-29 | Config as one JSON file, atomic writes, corrupt file quarantined to `config.bad-.json` | Simple, inspectable, crash-safe | SQLite; electron-store +- 2026-09-29 | Build Linux packages on Ubuntu 22.04 (Docker/CI) | node-pty links host glibc; 2.35 covers target distros | Building on Arch/Fedora +- 2026-09-29 | Shared CLAUDE.md/skills via links; conflicting files renamed to `.wraithgrid-backup` | Opt-in sharing without data loss or reading account files | Copying files; deleting conflicts +- 2026-09-29 | Update check reports only, from a hardcoded GitHub URL | Works for every package type; no SSRF from config | Auto-updater (electron-updater) +- 2026-09-29 | Platform logic as pure functions of `(platform, env)` | Unit-testable on any OS | Branching on `process.platform` inline +- 2026-09-30 | Adopt the AI Working Agreement (`CLAUDE.md`) + docs context files | Cut per-session re-exploration | None diff --git a/docs/TODO.md b/docs/TODO.md new file mode 100644 index 0000000..023883f --- /dev/null +++ b/docs/TODO.md @@ -0,0 +1,14 @@ +# TODO + +## Now +- (none) + +## Next +- (none recorded) + +## Later +- macOS support (REQUIREMENTS §4 lists it; only Windows + Linux ship today) +- Configurable shortcuts (REQUIREMENTS §7.5 "configurable later") + +## Blocked +- (none)