fix onboarding docs to match current architecture (T-101)

Rewrite README's Architecture section: drop the `ptyc/` description
and `make ptyc-build` line, fix the broken `decisions/` link
(governance moved it), add pointers to current docs.

Banner docs/initial-plan.md as historical with a redirect to a new
docs/architecture.md that describes today's shape (single Flutter
package at the repo root; in-process IPC + dispatcher + subsystems;
PTY via Dart FFI posix_openpt + posix_spawn).

Also banner two ptyc-era forensic notes (docs/pty-proposition.md,
docs/macos-pty-problem.md) as historical so a contributor reading
them isn't misled into thinking the C helper still exists.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-17 21:01:43 +02:00
co-authored by Claude Opus 4.7
parent 70ce6c270e
commit dd3b38ba85
7 changed files with 195 additions and 8 deletions
@@ -1709,3 +1709,7 @@ Why not always? Lock state at fork() time is timing-dependent. ~95% of the time
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-96', 'status', 'backlog', 'done', NULL, '2026-05-17 18:11:18', '2026-05-17 18:11:18', '2026-05-17 18:11:18', NULL, '3215fc763c4f1dce75ea9e0bb2b9bbb6', 1) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-98', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 18:51:25', '2026-05-17 18:51:25', '2026-05-17 18:51:25', NULL, '0369c8264ec2c67b3bf3f28057404ec0', 1) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-98', 'status', 'in_progress', 'done', NULL, '2026-05-17 18:55:57', '2026-05-17 18:55:57', '2026-05-17 18:55:57', NULL, 'cb0ae539d5665bd9720b327d60a2cbed', 1) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-101', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 18:58:56', '2026-05-17 18:58:56', '2026-05-17 18:58:56', NULL, '55d14b4493a159695d4b40bbd19fff23', 1) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-102', 'status', 'backlog', 'in_progress', NULL, '2026-05-17 18:58:56', '2026-05-17 18:58:56', '2026-05-17 18:58:56', NULL, '7fa075d7232df5302ffdfb5d1c3d79f2', 1) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-101', 'status', 'in_progress', 'done', NULL, '2026-05-17 19:01:39', '2026-05-17 19:01:39', '2026-05-17 19:01:39', NULL, '56e8da656c5fc53af241900e6313805e', 1) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-102', 'status', 'in_progress', 'done', NULL, '2026-05-17 19:01:39', '2026-05-17 19:01:39', '2026-05-17 19:01:39', NULL, '7f09d2a1501d3d072e071822c191f253', 1) ON CONFLICT(hash) DO NOTHING;
+16
View File
@@ -1607,3 +1607,19 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority,
3. Existing dugite-in-install-dir behavior preserved.
Source: consultants.md "Security — Findings — [Critical]".', 'done', 'critical', NULL, NULL, NULL, '2026-05-17 18:47:13', '2026-05-17 18:55:57', NULL, '40e1ed24c3cce28e2420150f1fac8fbd', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-102', 'bug', 'T-97', 'add symlink re-resolution + containment re-check to files.read / files.ls', '`path_safety.dart:35-51` explicitly does not resolve symlinks; the filesystem layer (`files_commands.dart:81-85`) doesn''t either. A repo symlink `config -> /etc/shadow` passes the containment check (the link path is under root) and clide reads the target.
**Fix:** after `resolveUnderRoot`, call `resolveSymbolicLinksSync()` and re-verify containment against the workspace root.
**Acceptance:** test that a symlink pointing outside the workspace is rejected by both `files.read` and `files.ls`.
Source: consultants.md "Security — Findings — [Major] Path-safety does not defend against symlink escape".', 'done', 'high', NULL, NULL, NULL, '2026-05-17 18:47:33', '2026-05-17 19:01:39', NULL, '937111bd0e00620e7faf68859018619a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-101', 'task', 'T-97', 'rewrite README + docs/initial-plan to match current architecture', '`README.md` and `docs/initial-plan.md` describe an architecture that no longer exists. README documents `ptyc/` as a live component (`README.md:10`) and `make ptyc-build` (`:36`) — `ptyc/` is gone (D-5 amendment 2026-05-07) and the Makefile has no such target. `README.md:44` links to a `decisions/` directory that has moved to `governance/`. `docs/initial-plan.md` still describes a Go sidecar, separate `clide --daemon` long-running process, `app/` subdirectory, and `project.yaml` — all contradicted by D-5, D-56, and the single-package-at-root reality. A new contributor''s first read builds a wrong mental model.
**Fix:**
1. Rewrite `README.md`''s `ptyc/` sections; remove `make ptyc-build` references.
2. Fix the `decisions/` link `governance/decisions/`.
3. Either banner `docs/initial-plan.md` as historical (preserved as a snapshot) or extract a current `docs/architecture.md` that matches today''s code.
4. Spot-check other doc references to dissolved components.
Source: consultants.md "Docs — Findings — [Critical]".', 'done', 'high', NULL, NULL, NULL, '2026-05-17 18:47:29', '2026-05-17 19:01:39', NULL, 'a7d378267119361f2b2188977881a666', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
+14 -8
View File
@@ -4,13 +4,14 @@ An IDE for Claude Code CLI. Native rendering, terminal-first interaction, pql-po
## Architecture
Single Flutter package at the repo root. The app hosts everything in-process: IPC server, subsystem handlers (pane, files, editor, git, pql), and the extension framework. tmux owns Claude session persistence.
Single Flutter package at the repo root. The app hosts everything in-process: IPC server, subsystem handlers (pane, files, editor, git, pql), and the extension framework. tmux owns Claude session persistence (D-41).
- **`lib/`** — all Dart code. Kernel services (theme, i18n, settings, panels, commands, focus), UI widgets, built-in extensions, and the extension contract.
- **`ptyc/`** — small C helper. Spawns a PTY + child and hands the master fd back over `SCM_RIGHTS`. Every pane (shell, tmux, claude, LSP, debug adapter) goes through it.
- **`lib/`** — all Dart code. Core subsystems (`lib/src/`), kernel services (`lib/kernel/`), UI widgets (`lib/widgets/`), built-in extensions (`lib/builtin/`), the extension framework (`lib/extension/`).
- **PTY** — `lib/src/pty/` spawns child processes via Dart FFI `posix_openpt()` + `posix_spawn()` directly; no external helper binary.
- **`native/`** — vendored native libraries (`libtree-sitter.so` with wasmtime embedded). Linux only today.
- **[pql](https://github.com/postmeridiem/pql)** — external supporter tool. Clide wraps it for every query surface; never re-implements it.
Claude drives the UI through a `clide` CLI surface (Bash, not MCP). Every CLI subcommand has a UI affordance and every UI action has a CLI equivalent.
Claude drives the UI through a `clide` CLI surface (Bash, not MCP). Every CLI subcommand has a UI affordance and every UI action has a CLI equivalent (D-6).
## Built-in extensions
@@ -30,19 +31,24 @@ Then:
make run # launch the desktop app
make test # fast suite: analyze + format + unit + widget + golden
make test-core # core subsystem tests (IPC, PTY, git, pane registry)
make test-a11y # accessibility contract tests
make test-integration # real app boot integration tests
make build-linux # flutter build linux
make build-macos # flutter build macos
make ptyc-build # build the ptyc PTY-spawn helper
make push-check # pre-push gate: decisions + core + fast tests
make push-check # pre-push gate: decisions + core + fast + a11y + coverage + changelog
```
## Status
Pre-v2.0 (`2.0.0-dev`). Interaction model and panel system landed. The Python Textual v1.2.0 predecessor is archived under [`legacy/`](https://github.com/postmeridiem/clide/tree/main/legacy).
Design doc: [`docs/initial-plan.md`](https://github.com/postmeridiem/clide/blob/main/docs/initial-plan.md). Architectural decisions: [`decisions/`](https://github.com/postmeridiem/clide/tree/main/decisions).
## Documentation
- [`docs/architecture.md`](docs/architecture.md) — current architecture (read this first).
- [`docs/initial-plan.md`](docs/initial-plan.md) — historical design doc; preserved as a snapshot of the 2026-04 plan, much of it now superseded.
- [`governance/decisions/`](governance/decisions/) — confirmed decisions (`D-NNN`), open questions (`Q-NNN`), rejected alternatives (`R-NNN`).
- [`CLAUDE.md`](CLAUDE.md) — Claude-addressed working notes (guardrails, repo layout).
## License
MIT. See [`LICENSE`](https://github.com/postmeridiem/clide/blob/main/LICENSE).
MIT. See [`LICENSE`](LICENSE).
+129
View File
@@ -0,0 +1,129 @@
# clide — Architecture
Current as of 2026-05-17. Tracks the code on `main`. For the original
design plan (much of it now superseded), see
[`docs/initial-plan.md`](initial-plan.md). For decisions, see
[`governance/decisions/`](../governance/decisions/).
## Shape
clide is a **single Flutter package at the repo root**. There is no
sidecar process, no separate daemon, no Go binary. One `flutter run`
boots the whole IDE.
```
flutter desktop app (lib/main.dart)
├── kernel services (lib/kernel/) — theme, i18n, settings, panels,
│ commands, focus, scheduler, …
├── core subsystems (lib/src/) — ipc, daemon dispatch, panes,
│ │ pty, editor, files, git, pql
│ └── PTY via Dart FFI posix_openpt + posix_spawn (T-96)
├── widget primitives (lib/widgets/) — ClideButton, ClideText, … no
│ Material/Cupertino
├── built-in extensions (lib/builtin/) — claude, editor, files, git, terminal,
│ welcome, … each a ClideExtension
└── extension framework (lib/extension/) — contract + dependency-gated activation
```
### Process model
One OS process. The Flutter app hosts:
- the IPC dispatcher (`DaemonDispatcher` in `lib/src/daemon/`),
- every subsystem handler (pane/files/editor/git/pql),
- the extension manager and all built-in extensions.
`tmux` is the only external long-lived process — it owns Claude
session persistence so panes survive app restarts (D-41). The app
re-attaches via `tmux new-session -A` on boot.
PTYs are spawned natively from Dart. `lib/src/pty/native_pty.dart`
calls `posix_openpt()` + `posix_spawn()` via FFI; the child inherits
the slave PTY as stdin/stdout/stderr. No C helper binary.
### Native dependencies
Vendored under [`native/`](../native/) with per-platform subdirectories
(`linux-x64/` today). Currently:
- `libtree-sitter.so` — wasmtime-embedded tree-sitter for syntax
highlighting. Loaded via `dart:ffi`. See
`lib/kernel/src/syntax/tree_sitter_ffi.dart`.
Each entry is listed in [`assets/licenses.yaml`](../assets/licenses.yaml)
with version + SHA expectation (D-42).
### External tools
clide shells out to two binaries at runtime:
- **`git`** — vendored as `dugite-native` if present at the install
directory; otherwise `PATH` `git`. Resolution happens at
`lib/kernel/src/toolchain_paths.dart`. **Never resolves against the
open workspace** (T-98).
- **`pql`** — the [pql](https://github.com/postmeridiem/pql) project
query language; supporter tool, wrapped in `lib/src/pql/`. Clide
never re-implements pql features (D-3).
## Surfaces
### Claude-facing — `clide` CLI
Per D-1 and D-6, Claude talks to clide exclusively via Bash. Every
state-changing command emits one or more events on a long-lived
event stream; every UI affordance has a matching CLI verb. See D-6
for the subsystem/verb/event contract.
> **Caveat (2026-05):** the Unix-socket server that exposes the
> dispatcher to a thin `clide` C client is currently unimplemented.
> Today's working path is in-process direct dispatch. See **T-99**
> (IPC server implementation) and **D-68** (dual integration surface
> — Bash CLI primary, MCP secondary).
### User-facing — Flutter desktop
Three-column layout (sidebar / workspace / context) with collapsible
panels, a custom title bar, and per-panel "hats" for branding +
window controls. The interaction model is documented in D-47 and
neighboring decisions.
## Subsystems at a glance
| Subsystem | Location | Owns |
|---|---|---|
| IPC envelope + dispatcher | `lib/src/ipc/`, `lib/src/daemon/` | request/response framing, command registration, broadcast events |
| Pane registry | `lib/src/panes/` | spawn/list/write/resize/close, event emission |
| PTY | `lib/src/pty/` | `posix_openpt` + `posix_spawn`, reader isolate, signal forwarding |
| Editor | `lib/src/editor/` | buffer registry, open/save/setContent |
| Files | `lib/src/files/` | ls / read / watch, path-safety containment check |
| Git | `lib/src/git/` | client (no shell), status/diff/operations parsing |
| Pql wrapper | `lib/src/pql/` | shell-out only; never re-implements pql |
| Kernel services | `lib/kernel/src/` | theme, i18n, settings, panels, commands, focus, syntax |
| Extensions | `lib/extension/`, `lib/builtin/` | dependency-gated activation, contribution points |
## Build + test
```
make hooks && flutter pub get # one-time setup
make run # launch app
make test # analyze + format + unit + widget + golden
make test-core # IPC / PTY / git / pane registry
make test-a11y # accessibility contract
make test-integration # real-app boot tests
make push-check # the full pre-push gate
```
The pre-push gate enforces a 95% line-coverage floor (D-66), a
40-word soft / 60-word hard CHANGELOG bullet cap, all unit/widget
tests, and the a11y contract.
## Governance
All architectural choices live in
[`governance/decisions/<domain>.md`](../governance/decisions/) as
`D-NNN` records, open questions as `Q-NNN`, rejected alternatives as
`R-NNN`. Claim new IDs via `pql decisions claim D <domain> "title"`.
The governance index lists everything: [`governance/README.md`](../governance/README.md).
The pql ticket backlog tracks in-flight work; `pql ticket board
--pretty` for the live view.
+19
View File
@@ -1,5 +1,24 @@
# Clide — Initial Plan (Flutter rebuild)
> **⚠ HISTORICAL — preserved as a snapshot of the 2026-04 plan.**
>
> Several load-bearing choices in this document have since been
> superseded by formal decisions:
>
> - **No Go sidecar.** Dart is the sole core language (D-5).
> - **No separate daemon process.** The Flutter app hosts the IPC
> server in-process (D-56).
> - **No `ptyc/` C helper.** PTY spawning uses Dart FFI
> `posix_openpt()` + `posix_spawn()` (D-5 amendments, T-96).
> - **Single package at the repo root**, not `app/` + `lib/` + `bin/`.
> - **No `project.yaml`.** Project metadata lives in `pubspec.yaml`.
>
> For the current architecture, see [`docs/architecture.md`](architecture.md).
> For the decision trail, see [`governance/decisions/`](../governance/decisions/).
> The text below is left intact for anyone tracing the design history.
---
**Working name:** clide (unchanged from the Python era). Repo root:
`/var/mnt/data/projects/clide`. Flutter desktop app + Go sidecar/CLI.
+6
View File
@@ -1,5 +1,11 @@
# macOS PTY Problem — Diagnosis Complete
> **⚠ HISTORICAL — `ptyc` is retired.** PTY spawning moved to
> Dart FFI `posix_openpt()` + `posix_spawn()` (D-5 amendments,
> T-96). The race described below was specific to the old C helper's
> SCM_RIGHTS hand-off and no longer applies.
## Status: Root cause found
The PTY master fd is valid (`isatty=1`), SCM_RIGHTS transfer is correct, all struct layouts are correct. The problem is **timing**: the reader isolate starts too late and the shell has already exited by the time `read()` is called on the master fd. macOS returns EOF (n=0) immediately when the slave side is closed — unlike Linux which buffers data.
+7
View File
@@ -1,5 +1,12 @@
# PTY on macOS: A New Diagnostic and Resolution Plan
> **⚠ HISTORICAL — `ptyc` is retired.** PTY spawning moved to
> Dart FFI `posix_openpt()` + `posix_spawn()` (D-5 amendments,
> T-96). This document captures the forensic investigation of the
> SCM_RIGHTS control-message mismatch in the old C helper. Kept for
> the diagnostic technique; the code it discusses no longer exists.
## 1. Executive Summary
Previous attempts to fix the PTY functionality on macOS have failed, even after correcting a deadlock in the Dart code. The core of the problem appears to be a fundamental mismatch in how the C helper (`ptyc`) constructs a control message and how the Dart FFI layer is trying to parse it.