From d38848feb8f41baf2fa4908972d5647e2e1f733e Mon Sep 17 00:00:00 2001 From: cachewraith Date: Wed, 30 Sep 2026 09:06:58 +0700 Subject: [PATCH] docs: add AI working agreement and project context files Adds a CLAUDE.md working agreement so each assistant session starts from a short project brief instead of re-scanning the repo. It also requires every task to leave the changelog, TODO list and decision log up to date. The context files were written after one pass over the repo: CONTEXT.md holds the directory map, commands, conventions and known gotchas; ARCHITECTURE.md covers the main/preload/renderer split, the IPC surface, data flow and env var names; TODO.md and CHANGELOG.md start the running logs. The decisions dated 2026-09-29 and the changelog history were reconstructed from the code and commits, not from design notes, and DECISIONS.md says so. They may need corrections from the author. --- CHANGELOG.md | 15 +++++++++ CLAUDE.md | 53 ++++++++++++++++++++++++++++++ docs/ARCHITECTURE.md | 61 +++++++++++++++++++++++++++++++++++ docs/CONTEXT.md | 76 ++++++++++++++++++++++++++++++++++++++++++++ docs/DECISIONS.md | 15 +++++++++ docs/TODO.md | 14 ++++++++ 6 files changed, 234 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 CLAUDE.md create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/CONTEXT.md create mode 100644 docs/DECISIONS.md create mode 100644 docs/TODO.md 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)