From 2235cc4c191113b18fb51a9d55472470a21ffe0d Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Thu, 23 Apr 2026 12:16:52 +0200 Subject: [PATCH] drop leading zeros from all D/Q/R/T IDs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pql 1.2.0 changed its record format to drop leading zeros. All 262 references across 18 files renumbered (D-001→D-1, T-043→T-43, etc.). pql-plan.json export updated as the git source of truth for the planning database. DB rebuilt via pql plan import. Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 72 +- CLAUDE.md | 18 +- POLICY.md | 2 +- decisions/README.md | 6 +- decisions/accessibility.md | 8 +- decisions/architecture.md | 152 +- decisions/extensions.md | 20 +- decisions/process.md | 28 +- decisions/questions-accessibility.md | 6 +- decisions/questions-architecture.md | 60 +- decisions/questions-extensions.md | 12 +- decisions/questions-process.md | 20 +- decisions/questions-testing.md | 6 +- decisions/questions.md | 8 +- decisions/rejected.md | 60 +- decisions/testing.md | 20 +- decisions/tooling.md | 16 +- pql-plan.json | 2759 ++++++++++++++++++++++++++ 18 files changed, 3016 insertions(+), 257 deletions(-) create mode 100644 pql-plan.json diff --git a/CHANGELOG.md b/CHANGELOG.md index d5f2b736..df24983c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,51 +26,51 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. pql Query (replaces custom input). - Interaction model from Wireframe Flows v3: eight new D-records - (D-047 through D-054) and five Q-records (Q-026 through Q-030) + (D-47 through D-54) and five Q-records (Q-26 through Q-30) codifying layout invariants, chrome budget, editor mode, context auto-behavior, collapse spine, focus mode, state persistence, and the canonical keyboard map. - Panel collapse spine — collapsed side panels render as a 12px vertical spine with rotated label, hover highlight, and badge dot - for pending context (D-051, T-030). + for pending context (D-51, T-30). - Focus mode — `Ctrl+.` takes the active panel full-window; `Escape` restores the prior layout with collapse states and - divider positions intact (D-052, T-031). + divider positions intact (D-52, T-31). - Canonical keyboard shortcuts from the interaction model: collapse toggles (`Ctrl+Shift+1/3`), panel focus (`Ctrl+1/2/3`), sidebar section switching (`Alt+1–5`), focus mode, and `Escape` dismiss - (D-054, T-033). + (D-54, T-33). - Right panel (context) icon rail — bottom section switcher matching - the left sidebar rail pattern (D-047, T-034). + the left sidebar rail pattern (D-47, T-34). - Editor-above-Claude mode — `Ctrl+E` opens the editor as a split above Claude in the middle column with a draggable divider; `Ctrl+W` or `Escape` closes it. Prompt bar Y stays fixed - (D-049, T-035). + (D-49, T-35). - Layout state persists across sessions — collapse state, sidebar and context panel sizes, active sections, and editor split ratio - saved to `.clide/settings.yaml` (D-053, T-032). + saved to `.clide/settings.yaml` (D-53, T-32). - Phosphor Icons font (v2.0.8, MIT) — regular, bold, and fill weights. Replaces hand-painted CustomPaint icons in sidebar and context panel icon rails. - Decisions panel in sidebar — lists confirmed D-records from - `pql decisions list` with ID and title (T-037). + `pql decisions list` with ID and title (T-37). - Tickets panel in sidebar — lists tickets from `pql ticket list` - with status dot color-coded by state (T-037). + with status dot color-coded by state (T-37). - Markdown viewer in context panel — shows raw content of the - active .md file, auto-updating on buffer switch (T-038). + active .md file, auto-updating on buffer switch (T-38). - Graph view in context panel — lists files with inbound/outbound - link counts from `pql search --connections` (T-039). + link counts from `pql search --connections` (T-39). - Welcome screen redesigned as full-screen overlay with two-column layout: START actions (open folder, clone, Claude session) with @@ -85,7 +85,7 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. ### Changed - Workspace renders Claude as the always-visible primary surface - instead of showing a tab bar (D-047, D-048). The editor is a + instead of showing a tab bar (D-47, D-48). The editor is a split overlay, not a tab. - Syntax highlighting via tree-sitter (dart:ffi to vendored @@ -99,7 +99,7 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. - Line length set to 160 across .editorconfig and dart formatter. -- Core frame vs shipped extension boundary defined (D-046). Builtins +- Core frame vs shipped extension boundary defined (D-46). Builtins are frame infrastructure only; content extensions are bundled but architecturally removable. @@ -121,7 +121,7 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. `.claude/settings.json`. - pql daemon subsystem (`lib/src/pql/`). `PqlClient` wraps the pql - CLI per D-003. IPC verbs `pql.files | meta | backlinks | outlinks + CLI per D-3. IPC verbs `pql.files | meta | backlinks | outlinks | tags | schema | query | doctor | decisions.sync | decisions.list | decisions.show | decisions.coverage | tickets.list | tickets.show | tickets.board | plan.status`. 15 new core tests. @@ -171,7 +171,7 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. echo-suppression guard avoids clobbering the caret when the daemon's authoritative edit echo comes back. Text surface is Flutter's `EditableText` primitive — no `TextField` / Material — - so the D-007 "no Material root" stance carries into the editor; + so the D-7 "no Material root" stance carries into the editor; JetBrainsMono via the shared `clideMonoFamily` constants, cursor + selection colours bind to the theme. @@ -204,7 +204,7 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. dispatcher round-trips. - `builtin.claude` — Tier-1 stub upgraded to the real Claude pane per - D-041. Contributes a primary `Claude` tab in the workspace slot that + D-41. Contributes a primary `Claude` tab in the workspace slot that spawns `tmux new-session -A -s clide-claude- -- claude` via IPC `pane.spawn`, with `` derived from the git root path so reopening the app re-attaches to the running conversation. Primary @@ -212,8 +212,8 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. Command `claude.new-secondary` is registered for the palette wiring that's coming next. If tmux isn't on PATH, falls back to spawning `claude` directly and surfaces "no-tmux · fresh every launch" in - the header subtitle. Accompanied by D-041 in - [`decisions/architecture.md`](decisions/architecture.md#d-041-claude-panes-one-primary-per-repo-tmux-backed). + the header subtitle. Accompanied by D-41 in + [`decisions/architecture.md`](decisions/architecture.md#d-41-claude-panes-one-primary-per-repo-tmux-backed). - `builtin.files` — workspace filesystem panel in the sidebar. Lazy tree rooted at the git root, expand/collapse, click-to-open plumbed @@ -223,7 +223,7 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. with ignore-file filtering. Ignore set composes clide's built-in hide list (`.git/`, `.pql/`, `.clide/`, `.dart_tool/`, `build/`, `node_modules/`) with `.gitignore` / `.clideignore` at the root per - D-004. `IgnoreSet` + `IgnorePattern` support line-per-pattern, `#` + D-4. `IgnoreSet` + `IgnorePattern` support line-per-pattern, `#` comments, anchored / directory-only / negated forms, and `**` across directories. 11 new unit tests on the matcher; 5 new dispatcher tests; 171 app tests still green. @@ -236,7 +236,7 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. `pane.resize` on viewport change. `initState` → spawn; `dispose` → `pane.close`. Error-state surface for "daemon not connected" / "shell exited." No Claude-specific behaviour — that - lives in `builtin.claude` + D-041. + lives in `builtin.claude` + D-41. - Shared pane widgets under `app/lib/widgets/`: `ClidePtyView` wraps `xterm.dart` with clide-theme token bindings, JetBrains Mono as the @@ -248,15 +248,15 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. no IPC coupling. - `xterm: 4.0.0` Dart dependency on the Flutter app — MIT, listed in - `licenses.yaml` per D-042. Hand-rolling a VT100 / xterm / truecolour + `licenses.yaml` per D-42. Hand-rolling a VT100 / xterm / truecolour parser + renderer would be weeks for no fidelity win. -- `Q-023` — open question on SSH-remote development (run clide against +- `Q-23` — open question on SSH-remote development (run clide against a workspace on another host). Local-first stays the Tier-1 target; this records the constraint so the daemon / IPC / extension seams don't unknowingly accrete local-only assumptions. -- IPC `pane` subsystem in the daemon (per D-006). Commands: +- IPC `pane` subsystem in the daemon (per D-6). Commands: `pane.spawn | list | focus | close | write | resize | tail`. Events: `pane.spawned`, `pane.output` (base64-framed), `pane.exit`, `pane.resized`, `pane.focused`, `pane.closed`. `PaneRegistry` owns @@ -281,7 +281,7 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. verification, idempotent close. - `ffi: 2.1.3` as a runtime dependency on the Dart core — justified - in `pubspec.yaml` + documented in `licenses.yaml` per D-042. Used + in `pubspec.yaml` + documented in `licenses.yaml` per D-42. Used by `lib/src/pty/ffi/` for `socketpair`, `recvmsg` with `SCM_RIGHTS`, `read`/`write` on raw fds, and `ioctl(TIOCSWINSZ)`. @@ -312,7 +312,7 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. `license_file` pointer, and a one-line purpose. Bundled alongside the per-dep license texts. The About screen (Tier 6) will render this file verbatim. Accompanied by - [`D-042`](decisions/tooling.md#d-042-bundled-dependencies-documented-in-licensesyaml): + [`D-42`](decisions/tooling.md#d-42-bundled-dependencies-documented-in-licensesyaml): adding a dep is a two-step commit (artefact + `licenses.yaml` entry in the same changeset). @@ -320,10 +320,10 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. - CLAUDE.md "Dependencies & supply chain" section gains the "document every bundled dependency" rule, pointing at - `app/assets/licenses.yaml` and `D-042`. + `app/assets/licenses.yaml` and `D-42`. - `ptyc/` — the C PTY-spawn helper, peer of `pql` per - [`D-005`](decisions/architecture.md#d-005-dart-core-sidecar-dissolved-ptyc-as-pql-peer). + [`D-5`](decisions/architecture.md#d-5-dart-core-sidecar-dissolved-ptyc-as-pql-peer). One-shot, libc-only, ~400 LOC. Reads a JSON request on stdin (`argv`, optional `cwd`/`env`/`cols`/`rows`), does `posix_openpt` + `fork` + `execvp`, and hands the master fd back @@ -336,10 +336,10 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. `ptyc-build` / `ptyc-clean`. - Migrated the `docs/ADRs/` content into `decisions/` as D/R records: - ADR 0001 → `D-001`, ADR 0002 → `R-002` (superseded by `D-005`), ADR - 0003 → `D-003`, ADR 0004 → `D-004`, ADR 0005 → `D-005`, ADR 0006 → - `D-006`. Titles preserved; ADR 0006's trailing open questions moved - to `questions-architecture.md` as `Q-001` / `Q-002` / `Q-003`. The + ADR 0001 → `D-1`, ADR 0002 → `R-2` (superseded by `D-5`), ADR + 0003 → `D-3`, ADR 0004 → `D-4`, ADR 0005 → `D-5`, ADR 0006 → + `D-6`. Titles preserved; ADR 0006's trailing open questions moved + to `questions-architecture.md` as `Q-1` / `Q-2` / `Q-3`. The originals are preserved in git history. - `decisions/` at the repo root — Q&D record system ported from @@ -364,8 +364,8 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. (`tools/scripts/plan ` → `pql `). Ported from settled-reach with the Scrum layer stripped; ticket IDs are `T-NNN` (TEXT PKs) and there's no `sprints` table. Time-limited per - [`D-040`](decisions/process.md#d-040-python-stopgap-under-toolsscriptsplan) - / [`R-011`](decisions/rejected.md#r-011-permanent-stopgap). + [`D-40`](decisions/process.md#d-40-python-stopgap-under-toolsscriptsplan) + / [`R-11`](decisions/rejected.md#r-11-permanent-stopgap). - `make decisions-validate` — cheap parser dry-run wired into `push-check`. Catches malformed records before push. @@ -373,7 +373,7 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. - Reserved extension slots — `builtin.decisions`, `builtin.tickets`, `builtin.claude-control`. Id-reserving stubs under `app/lib/builtin/` with no contributions yet. Implementations land - once [`Q-021`](decisions/questions-architecture.md#q-021-pql-absorbs-planning-vs-keeps-separate) + once [`Q-21`](decisions/questions-architecture.md#q-21-pql-absorbs-planning-vs-keeps-separate) resolves (decisions + tickets) or when the claude-control tier arrives (`.claude/` first-class surface — distinct from the existing `builtin.claude` PTY-pane stub). @@ -400,9 +400,9 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. - `tools/scripts/plan` — Python stopgap planning scripts, superseded by `pql` 1.0 native `decisions` and `ticket` subcommands. Sunset condition from - [`D-040`](decisions/process.md#d-040-python-stopgap-under-toolsscriptsplan) + [`D-40`](decisions/process.md#d-40-python-stopgap-under-toolsscriptsplan) met; deletion per - [`R-011`](decisions/rejected.md#r-011-permanent-stopgap). + [`R-11`](decisions/rejected.md#r-11-permanent-stopgap). ### Removed diff --git a/CLAUDE.md b/CLAUDE.md index a254ff56..9e3b9ad4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,11 +6,11 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co A Flutter desktop IDE for Claude Code. Single Flutter package at the repo root, plus small native supporter tools where Dart can't reach. -- **`lib/`** — all Dart code. Subsystem handlers (`lib/src/daemon/`, `lib/src/pty/`, `lib/src/ipc/`, `lib/src/git/`, `lib/src/pql/`), kernel services (`lib/kernel/`), UI widgets (`lib/widgets/`), built-in extensions (`lib/builtin/`), and the extension framework (`lib/extension/`). The Flutter app hosts the IPC server in-process (D-056). +- **`lib/`** — all Dart code. Subsystem handlers (`lib/src/daemon/`, `lib/src/pty/`, `lib/src/ipc/`, `lib/src/git/`, `lib/src/pql/`), kernel services (`lib/kernel/`), UI widgets (`lib/widgets/`), built-in extensions (`lib/builtin/`), and the extension framework (`lib/extension/`). The Flutter app hosts the IPC server in-process (D-56). - **[`pql`](https://github.com/postmeridiem/pql)** — external supporter tool. Clide wraps it for every query surface; never re-implements it. - **`ptyc/`** — small C supporter tool, peer of pql. Spawns a PTY + child and hands the master fd back over `SCM_RIGHTS`. Clide shells out to it for every pane (shell, tmux, claude, LSP, debug adapter). -tmux owns Claude session persistence (D-041) — the app re-attaches on restart via `tmux new-session -A`. Native rendering — markdown, canvas, graph — is Dart/Flutter (`CustomPaint` + widgets), not third-party packages. +tmux owns Claude session persistence (D-41) — the app re-attaches on restart via `tmux new-session -A`. Native rendering — markdown, canvas, graph — is Dart/Flutter (`CustomPaint` + widgets), not third-party packages. Design doc: [`docs/initial-plan.md`](docs/initial-plan.md). Decisions: [`decisions/`](decisions/) (`D-NNN` confirmed, `Q-NNN` open, `R-NNN` rejected — see [`decisions/README.md`](decisions/README.md)). Python Textual predecessor under [`legacy/`](legacy/). @@ -19,14 +19,14 @@ Design doc: [`docs/initial-plan.md`](docs/initial-plan.md). Decisions: [`decisio These are load-bearing. Violating any means the design is wrong, not the rule. - **Flutter desktop is the host. No Electron, ever.** Web target may work as a happy accident — don't compromise desktop fidelity for it. If we ship a web build at all, prefer Flutter's **WebAssembly (CanvasKit/Skwasm) compile** over the JS/HTML renderer. `xterm.dart` is the terminal renderer; markdown, canvas, graph are custom `CustomPaint`/widget components. -- **Single process.** The Flutter app hosts everything in-process: IPC server, subsystem handlers (pane, files, editor, git, pql), extensions. No separate daemon binary (D-056 dissolved it). The CLI surface for Claude is a thin C client (ptyc peer). -- **CLI-first, not MCP.** Claude talks via Bash (`clide ...`), matching pql's contract. See [`D-001`](decisions/architecture.md#d-001-cli-first-not-mcp). -- **Dart is the core; native supporter tools fill specific gaps.** `ptyc` (C) for PTY spawning. `pql` (Go) for queries. No second "core language." See [`D-005`](decisions/architecture.md#d-005-dart-core-sidecar-dissolved-ptyc-as-pql-peer) (amended by D-056). +- **Single process.** The Flutter app hosts everything in-process: IPC server, subsystem handlers (pane, files, editor, git, pql), extensions. No separate daemon binary (D-56 dissolved it). The CLI surface for Claude is a thin C client (ptyc peer). +- **CLI-first, not MCP.** Claude talks via Bash (`clide ...`), matching pql's contract. See [`D-1`](decisions/architecture.md#d-1-cli-first-not-mcp). +- **Dart is the core; native supporter tools fill specific gaps.** `ptyc` (C) for PTY spawning. `pql` (Go) for queries. No second "core language." See [`D-5`](decisions/architecture.md#d-5-dart-core-sidecar-dissolved-ptyc-as-pql-peer) (amended by D-56). - **Own the rendering stack.** PTY (via `ptyc`), markdown renderer, graph, canvas — all clide-owned, not pulled from opinionated packages. -- **User/Claude parity.** Every CLI subcommand has a UI affordance, and every UI action has a CLI. See [`D-006`](decisions/architecture.md#d-006-cli-and-event-surface-contract). -- **pql: wrap, don't duplicate.** Pql logic only lives in `lib/src/pql/` (pure shell-outs). Clide owns pql's `ignore_files:` config key; it never touches pql's `.pql/` index/cache data. See [`D-003`](decisions/architecture.md#d-003-pql-as-supporter-tool-clide-wraps-never-duplicates). +- **User/Claude parity.** Every CLI subcommand has a UI affordance, and every UI action has a CLI. See [`D-6`](decisions/architecture.md#d-6-cli-and-event-surface-contract). +- **pql: wrap, don't duplicate.** Pql logic only lives in `lib/src/pql/` (pure shell-outs). Clide owns pql's `ignore_files:` config key; it never touches pql's `.pql/` index/cache data. See [`D-3`](decisions/architecture.md#d-3-pql-as-supporter-tool-clide-wraps-never-duplicates). - **Repo-is-the-workspace.** The git repo root is the workspace — no parallel "vault" concept. -- **Ignore discipline.** Single knob: `ignore_files:` in `.pql/config.yaml`, ordered layering. See [`D-004`](decisions/architecture.md#d-004-ignore-file-strategy). +- **Ignore discipline.** Single knob: `ignore_files:` in `.pql/config.yaml`, ordered layering. See [`D-4`](decisions/architecture.md#d-4-ignore-file-strategy). - **Decision discipline.** All architectural choices live in `decisions/.md` as `D-NNN` records. Open questions as `Q-NNN`. Rejected alternatives as `R-NNN`. Claim new IDs via `pql decisions claim D "title"`. See [`decisions/README.md`](decisions/README.md). ## Repo layout @@ -55,7 +55,7 @@ legacy/ # Python Textual clide v1.2 (frozen) ## Dependencies & supply chain - **Prefer-zero-deps.** Flutter-SDK widgets first; third-party packages need justification. What stays is exact-pinned in `pubspec.yaml` (no caret ranges). Advisories reviewed before every bump; `pubspec.lock` committed. -- **Document every bundled dependency.** Listed in [`assets/licenses.yaml`](assets/licenses.yaml) with name, kind, version, homepage, license, and purpose. Adding a dep is a two-step commit: add the artefact **and** the `licenses.yaml` entry. See [`D-042`](decisions/tooling.md#d-042-bundled-dependencies-documented-in-licensesyaml). +- **Document every bundled dependency.** Listed in [`assets/licenses.yaml`](assets/licenses.yaml) with name, kind, version, homepage, license, and purpose. Adding a dep is a two-step commit: add the artefact **and** the `licenses.yaml` entry. See [`D-42`](decisions/tooling.md#d-42-bundled-dependencies-documented-in-licensesyaml). - **`ptyc` and any future native supporter tool:** no dep graph by design (libc-only for `ptyc`). "Audit" is reading the source before each bump. ## Commands diff --git a/POLICY.md b/POLICY.md index c4b83215..4d4050be 100644 --- a/POLICY.md +++ b/POLICY.md @@ -63,7 +63,7 @@ This is not a preference. It is the project's architectural stance, documented i - **Prefer inlining over depending.** If a package is small and does one thing we need, copy the relevant code (with attribution) rather than taking on the dependency. License permitting, this is usually the right call for utilities under a few hundred lines. - **Prefer vendoring over fetching.** When a dependency is unavoidable and small enough to own, fork it into the repo and maintain it ourselves. The dep graph stops at our fence. - **Reject deep trees.** A direct dependency that itself has fifteen transitive dependencies is fifteen dependencies we are taking on, not one. Evaluate the whole subtree, not just the top. -- **Format engines clear the bar.** Packages that parse or render external file formats (SVG, markdown, HTML, terminal escapes, tree-sitter grammars) are adoptable — they are not shortcuts for lazy coding but maintained renderers for specs we didn't write. The distinction: UI chrome (panels, tabs, canvas, layout) we own; someone else's file format we adopt and sandbox. See D-058. +- **Format engines clear the bar.** Packages that parse or render external file formats (SVG, markdown, HTML, terminal escapes, tree-sitter grammars) are adoptable — they are not shortcuts for lazy coding but maintained renderers for specs we didn't write. The distinction: UI chrome (panels, tabs, canvas, layout) we own; someone else's file format we adopt and sandbox. See D-58. ### Every dependency is audited diff --git a/decisions/README.md b/decisions/README.md index 0f622623..0c81deef 100644 --- a/decisions/README.md +++ b/decisions/README.md @@ -49,7 +49,7 @@ Domain-specific fields (`Kill switch:`, `Evaluation reports:`, `Amendment:`, `Cross-reference:`) are additive. Amendments are inline and dated: `**Amendment (YYYY-MM-DD):** …`. Cross-references use markdown anchor links with the full slug: -`[D-005](architecture.md#d-005-dart-core-ptyc-peer)`. +`[D-5](architecture.md#d-5-dart-core-ptyc-peer)`. Open questions (`Q-NNN`): @@ -73,7 +73,7 @@ Rejected alternatives (`R-NNN`): ## Claiming an ID -Until the pql planning subcommands land ([`Q-021`](questions-process.md)), +Until the pql planning subcommands land ([`Q-21`](questions-process.md)), claim IDs by inspecting the highest existing `D-NNN` / `Q-NNN` / `R-NNN` in the target file and incrementing. @@ -89,7 +89,7 @@ Common queries: ```bash pql decisions list --type confirmed --domain architecture -pql decisions show D-005 --with-refs +pql decisions show D-5 --with-refs pql decisions coverage # D-records without tickets pql decisions validate # pre-push parser gate pql ticket board # kanban view of tickets diff --git a/decisions/accessibility.md b/decisions/accessibility.md index 0f6c2cfd..59a0b66e 100644 --- a/decisions/accessibility.md +++ b/decisions/accessibility.md @@ -4,21 +4,21 @@ A11y + i18n are Tier-0 contracts, not Tier-6 polish. --- -### D-020: A11y is a Tier-0 contract +### D-20: A11y is a Tier-0 contract - **Date:** 2026-04-21 - **Decision:** Every widget primitive wraps its interaction surface in a `Semantics` node at the point of creation. A11y coverage is a test-time gate (`ci/test_a11y.sh`), not a post-hoc polish pass. `ensureSemantics()` fires at app boot; Flutter's semantics tree is always populated. - **Rationale:** Retrofitting a11y onto a grown UI is what every project that skips this promises to do later and then doesn't. Making it a Tier-0 contract costs one `Semantics` line per primitive and a semantic-coverage test; postponing costs a rewrite. - **Cost:** Widget authors maintain correct labels; tests reject new primitives without semantics. Enforced by `app/test/a11y/` coverage tests. - **Raised by:** 2026-04-21 planning. -### D-021: i18n is a Tier-0 contract (fframe pattern + locale-fallback chain) +### D-21: i18n is a Tier-0 contract (fframe pattern + locale-fallback chain) - **Date:** 2026-04-21 - **Decision:** All user-facing strings resolve through a namespaced i18n catalogue loader ported from fframe's text-driven pattern, extended with a locale-fallback chain fframe lacks. JSON per locale; `I18n.of(context).t('namespace.key', {vars})`. Missing keys resolve down the chain (e.g. `en_GB` → `en` → default), never fail silently; missing at the base locale logs a dev-mode error. -- **Rationale:** Flutter's `intl` + ARB codegen is inflexible for plugin-contributed catalogs (see [R-004](rejected.md#r-004-flutter-intl-and-arb-codegen)) — we need per-extension catalogs that merge without a codegen step. fframe's shape fits; its silent-fallback behaviour does not, so we add the chain. +- **Rationale:** Flutter's `intl` + ARB codegen is inflexible for plugin-contributed catalogs (see [R-4](rejected.md#r-4-flutter-intl-and-arb-codegen)) — we need per-extension catalogs that merge without a codegen step. fframe's shape fits; its silent-fallback behaviour does not, so we add the chain. - **Cost:** JSON has no comments and no trailing commas; translation tooling has to accept that. Separate `i18n` facade on every feature. - **Raised by:** 2026-04-21 planning. -### D-022: WCAG-AA contrast gate on bundled themes +### D-22: WCAG-AA contrast gate on bundled themes - **Date:** 2026-04-21 - **Decision:** Every bundled theme must pass a WCAG-AA contrast check on its canonical token pairs (text/background, link/background, focus-ring/background) at test time. `ci/test_a11y.sh` runs the gate; CI fails on regressions. - **Rationale:** Themes drift under "looks nicer" tweaks; contrast regressions land silently. Running the gate on every PR is the cheapest insurance. Ran the gate on initial themes — caught one summer-night muted token at 2.81:1 (below AA), fixed before landing. diff --git a/decisions/architecture.md b/decisions/architecture.md index a0de0a1a..99d75762 100644 --- a/decisions/architecture.md +++ b/decisions/architecture.md @@ -4,56 +4,56 @@ Core, rendering, IPC, kernel, panel manager. --- -### D-007: App root is bare `WidgetsApp` +### D-7: App root is bare `WidgetsApp` - **Date:** 2026-04-21 - **Decision:** The Flutter app root is `WidgetsApp`, not `MaterialApp` or `CupertinoApp`. Clide's look is fully custom; the Material/Cupertino shells would drag in opinionated theming, default icons, and platform chrome we'd then have to fight. -- **Rationale:** Clide is a Linux-primary desktop IDE with a custom theme pipeline and custom primitives (panels, tabs, panes, canvas). Material's implicit theming collides with [D-009](#d-009-three-tier-theme-pipeline); Cupertino is iOS-flavoured. `WidgetsApp` gives us routing, locale, focus traversal, semantics, and Directionality without aesthetic baggage. -- **Cost:** We build and own every primitive; no `ElevatedButton` fallback. See [R-003](rejected.md#r-003-materialapp-root) and [R-007](rejected.md#r-007-cupertinoapp-root). +- **Rationale:** Clide is a Linux-primary desktop IDE with a custom theme pipeline and custom primitives (panels, tabs, panes, canvas). Material's implicit theming collides with [D-9](#d-9-three-tier-theme-pipeline); Cupertino is iOS-flavoured. `WidgetsApp` gives us routing, locale, focus traversal, semantics, and Directionality without aesthetic baggage. +- **Cost:** We build and own every primitive; no `ElevatedButton` fallback. See [R-3](rejected.md#r-3-materialapp-root) and [R-7](rejected.md#r-7-cupertinoapp-root). - **Raised by:** 2026-04-21 planning. -### D-008: Feature-first folder layout +### D-8: Feature-first folder layout - **Date:** 2026-04-21 - **Decision:** Under `app/lib/`, organise by feature (`kernel/`, `extension/`, `widgets/`, `builtin//`) rather than by layer (`models/`, `views/`, `controllers/`). Private implementation lives under each feature's `src/`; the feature's public surface is a barrel file at the feature root (e.g. `app/lib/kernel/kernel.dart`). - **Rationale:** Features grow and get deleted as units; layer-first layouts fragment a feature across three directories and make deletions risky. Matches extensions-as-features (every extension already has its own folder). - **Cost:** Imports cross features only via the barrel — enforce by review, no automated check yet. - **Raised by:** 2026-04-21 planning. -### D-009: Three-tier theme pipeline +### D-9: Three-tier theme pipeline - **Date:** 2026-04-21 - **Decision:** Themes resolve through three layers: (1) palette — raw named colours per theme YAML; (2) semantic — roles like `surface.background`, `text.primary`, `accent.focus`; (3) surface — component-scoped tokens derived from semantic roles (button bg/fg/border hover/pressed/disabled states). - **Rationale:** Direct palette-to-component binding collapses under multi-theme work; VS Code's 600-token surface map is the proof. The semantic layer is where a11y contrast gates apply; the surface layer is where components bind. -- **Cost:** Three layers to keep coherent per theme. Contrast gate ([D-022](accessibility.md#d-022-wcag-aa-contrast-gate-on-bundled-themes)) enforces the semantic layer on every bundled theme. +- **Cost:** Three layers to keep coherent per theme. Contrast gate ([D-22](accessibility.md#d-22-wcag-aa-contrast-gate-on-bundled-themes)) enforces the semantic layer on every bundled theme. - **Raised by:** 2026-04-21 planning. -### D-010: State management — `ChangeNotifier` + `ListenableBuilder` +### D-10: State management — `ChangeNotifier` + `ListenableBuilder` - **Date:** 2026-04-21 - **Decision:** Per-feature state uses `ChangeNotifier` exposed through a feature facade (singleton-per-kernel); widgets subscribe via `ListenableBuilder`. No Riverpod, Provider, BLoC, or Redux. -- **Rationale:** SDK-shipped, zero deps, trivial to fake in tests (hand-rolled fakes in [D-025](testing.md#d-025-mocks-mocktail-at-io-plus-hand-rolled-fakes)). Violates [D-031 prefer-zero-deps](tooling.md#d-031-prefer-zero-deps-exact-pin) otherwise. See [R-008](rejected.md#r-008-riverpod-provider-bloc-for-state). +- **Rationale:** SDK-shipped, zero deps, trivial to fake in tests (hand-rolled fakes in [D-25](testing.md#d-25-mocks-mocktail-at-io-plus-hand-rolled-fakes)). Violates [D-31 prefer-zero-deps](tooling.md#d-31-prefer-zero-deps-exact-pin) otherwise. See [R-8](rejected.md#r-8-riverpod-provider-bloc-for-state). - **Cost:** No codegen ergonomics; manual `notifyListeners()` discipline. The `ListenableBuilder.listenable` contract rejects rebuilds outside the subscribed notifier — intentional. - **Raised by:** 2026-04-21 planning. -### D-011: Panel manager is kernel; layout is data; three-column is a preset +### D-11: Panel manager is kernel; layout is data; three-column is a preset - **Date:** 2026-04-21 - **Decision:** The kernel owns a panel manager that treats layout as declarative data (tree of splits + leaves). The default "three-column IDE" (sidebar / editor / assistant) is one preset; alternative presets (writer-focus single-column, debugger four-pane) ship as data, not code forks. - **Rationale:** Hard-coded three-column layouts paint us into corners when future tiers add canvas, graph, terminal-grid. Data-driven layout also lets extensions contribute presets without patching the panel manager. - **Cost:** More kernel surface up-front; pays back at Tier 5 (canvas) and Tier 6 (extension-contributed layouts). - **Raised by:** 2026-04-21 planning. -### D-012: Kernel admission rule — mandatory shared singletons only +### D-12: Kernel admission rule — mandatory shared singletons only - **Date:** 2026-04-21 - **Decision:** A service joins the kernel only if it is (a) mandatory for app boot and (b) a shared singleton across features. Everything else is an extension or a feature-local service. - **Rationale:** Keeps the kernel auditable. Previous drafts piled "useful globals" into the kernel; result was a 40-service god-object. The admission rule forced 18 services out of 31 candidates. - **Cost:** Some legitimate cross-cutting concerns (telemetry, crash reporter when they land) must pass the test; we expect a few more admissions as Tiers 3-6 land. - **Raised by:** 2026-04-21 planning. -### D-013: Git hardcoded in kernel project-loader +### D-13: Git hardcoded in kernel project-loader - **Date:** 2026-04-21 - **Decision:** The kernel's project loader treats "repo root" as a `git` concept — runs `git rev-parse --show-toplevel` to find workspace root, subscribes to filesystem events, and shells out to `git` for status/diff/stage. No VCS abstraction layer. - **Rationale:** Option B (VCS abstraction) is premature generalisation — we have one VCS today, Mercurial/Fossil/Sapling users are a rounding error on the Linux desktop IDE market, and the abstraction adds a seam that has to be tested against nothing. When a second VCS shows up we refactor. - **Cost:** Adding Mercurial support later costs a real refactor, not just a plugin. Acceptable. - **Raised by:** 2026-04-21 planning. -### D-014: Two-tier disable — kernel locked, everything else extension-shaped +### D-14: Two-tier disable — kernel locked, everything else extension-shaped - **Date:** 2026-04-21 - **Decision:** Kernel services cannot be disabled at runtime. Extensions (including every bundled built-in) can be toggled via the extension manager. This creates exactly two disable tiers: kernel (always on) and extension (toggleable). - **Rationale:** A three-tier system (kernel / bundled-cannot-disable / user-can-disable) is dishonest — if a "bundled built-in" can't be disabled, it's kernel and belongs in kernel admission review. Forcing every bundled feature to pass the extension contract is also the best test we have that the contract is actually usable. @@ -62,156 +62,156 @@ Core, rendering, IPC, kernel, panel manager. --- -### D-041: Claude panes — one primary per repo, tmux-backed +### D-41: Claude panes — one primary per repo, tmux-backed - **Date:** 2026-04-22 - **Decision:** Every repo (keyed on the git root) hosts **exactly one primary Claude pane** plus zero or more **secondary** Claude panes. The primary persists across clide restarts; secondaries are ephemeral. Persistence layer is **tmux**: the daemon spawns the primary as `tmux new-session -A -s clide-claude- -- claude`, which re-attaches to the running session if the app restarts. Secondaries spawn as `tmux new-session -A -s clide-claude--N -- claude` with `N` incrementing. Close semantics: closing a secondary kills that tmux session and focus collapses back to the primary (or to the next-most-recent secondary); the primary has **no close affordance** — close-gestures on it hide it / minimise to a dock, they don't kill the session. Daemon is the owner; the UI doesn't track tmux session state directly, it just asks the pane subsystem to spawn/close and observes events. General-purpose terminal panes (`builtin.terminal`) do **not** get tmux wrapping or persistence — they're per-app-lifetime. - **Context:** 2026-04-22 planning. The user workflow is "open repo → Claude is already there, with my last conversation intact." A cold session-restart every time clide re-launches defeats the premise. tmux already solves "reattach to a shell-like session across disconnects"; layering our own persistence protocol on top of ptyc would duplicate it. - **Rationale:** (1) tmux is battle-tested — no new persistence code to review. (2) The pane subsystem stays neutral; Claude-specific behaviour lives in `builtin.claude`. (3) Keying by git root means the user doesn't manage session names manually — opening a repo is enough. (4) "Always one primary" removes a failure mode: there's never "no Claude to talk to." (5) Secondaries stay frictionless — the user spawns and closes them at will without breaking the primary. - **Cost:** Requires tmux on the PATH of the daemon's runtime environment (reasonable for Linux + macOS; Windows support via WSL or a separate approach). Killing a primary (via the daemon on shutdown) still leaves the detached tmux session around until the next clide start re-attaches; acceptable but worth documenting for support. Secondary numbering (`-1`, `-2`, …) resets between clide runs since ephemeral state is lost — also acceptable. - **Raised by:** 2026-04-22 planning, Tier 1 implementation. -- **Cross-reference:** [`D-005`](#d-005-dart-core-sidecar-dissolved-ptyc-as-pql-peer) (ptyc as the spawn primitive tmux runs under), [`D-006`](#d-006-cli-and-event-surface-contract) (pane.\* IPC surface), [`R-009`](rejected.md#r-009-port-planning-tooling-into-clide) (why per-repo scoping via git root matches the wrap-don't-duplicate theme). +- **Cross-reference:** [`D-5`](#d-5-dart-core-sidecar-dissolved-ptyc-as-pql-peer) (ptyc as the spawn primitive tmux runs under), [`D-6`](#d-6-cli-and-event-surface-contract) (pane.\* IPC surface), [`R-9`](rejected.md#r-9-port-planning-tooling-into-clide) (why per-repo scoping via git root matches the wrap-don't-duplicate theme). -### D-001: CLI-first, not MCP +### D-1: CLI-first, not MCP - **Date:** 2026-04-20 (was ADR 0001; ported from the claudian lineage) - **Decision:** Claude talks to clide exclusively via Bash (`clide …`). No MCP server. No protocol layer in Claude's face. The CLI uses the same exit-code + stderr-JSON contract as pql. - **Context:** The two mainstream options for the agent-facing surface were an MCP server or a plain Bash CLI matching pql's contract. -- **Rationale:** Same mental model as pql for the agent — one tool-use pattern covers both. No MCP runtime to host, authenticate, or keep in sync with client versions. User/Claude parity is easier to enforce: every CLI subcommand must have a UI affordance in the Flutter app and vice versa ([D-006](#d-006-cli-and-event-surface-contract)). Claude Code's `Bash(clide *)` allow rule is the only configuration clide needs on the agent side. +- **Rationale:** Same mental model as pql for the agent — one tool-use pattern covers both. No MCP runtime to host, authenticate, or keep in sync with client versions. User/Claude parity is easier to enforce: every CLI subcommand must have a UI affordance in the Flutter app and vice versa ([D-6](#d-6-cli-and-event-surface-contract)). Claude Code's `Bash(clide *)` allow rule is the only configuration clide needs on the agent side. - **Cost:** If an MCP-only integration becomes compelling later (e.g. a multi-agent scenario), nothing here precludes adding one that shells out to the same CLI. - **Raised by:** Ported from the claudian lineage. -### D-003: pql as supporter tool; clide wraps, never duplicates +### D-3: pql as supporter tool; clide wraps, never duplicates - **Date:** 2026-04-20 (was ADR 0003; ported from the claudian lineage) -- **Decision:** Two complementary rules. **(1) Wrap, don't duplicate.** Clide never re-implements backlinks, ranking, frontmatter parsing, or wikilink resolution for query purposes. If a capability is missing in pql, it is added upstream in pql's repo and clide bumps the dependency. The only place clide contains pql logic is `lib/src/pql/` — pure shell-outs to the `pql` binary. **(2) pql is a clide subsystem when clide is present in the repo.** On load, clide writes its current state into `.pql/config.yaml` — no conditional sync. Clide only stomps keys it manages (starting with `ignore_files:` — see [D-004](#d-004-ignore-file-strategy)). Other pql config keys are left alone. Clide does **not** touch pql's index/cache data under `/.pql/` — that stays pql's private store. +- **Decision:** Two complementary rules. **(1) Wrap, don't duplicate.** Clide never re-implements backlinks, ranking, frontmatter parsing, or wikilink resolution for query purposes. If a capability is missing in pql, it is added upstream in pql's repo and clide bumps the dependency. The only place clide contains pql logic is `lib/src/pql/` — pure shell-outs to the `pql` binary. **(2) pql is a clide subsystem when clide is present in the repo.** On load, clide writes its current state into `.pql/config.yaml` — no conditional sync. Clide only stomps keys it manages (starting with `ignore_files:` — see [D-4](#d-4-ignore-file-strategy)). Other pql config keys are left alone. Clide does **not** touch pql's index/cache data under `/.pql/` — that stays pql's private store. - **Context:** [`pql`](https://github.com/postmeridiem/pql) is a pre-existing Go CLI that indexes a markdown-bearing directory tree into SQLite and exposes frontmatter, wikilinks, tags, headings, and bases through a query surface. Clide needs those capabilities for its Query panel, canvas drivers, graph view, and any feature that needs to know structure. - **Rationale:** One source of truth for markdown semantics. Any new query capability the UI wants goes through a pql upstream PR, not a local workaround. Users never have to learn pql's config file to get consistent behaviour — clide manages it. The arrow clide → pql is never inverted: pql stays ignorant of its wrapper. - **Cost:** Clide's `lib/src/pql/` package is deliberately thin. pql is also the **only** query engine — Obsidian-style inline "bases" are explicitly not supported; queries live at the repo level. In repos without clide, pql works standalone unaffected. -- **Raised by:** Ported from the claudian lineage. Load-bearing for [D-039](process.md#d-039-planning-tooling-lives-in-pql). +- **Raised by:** Ported from the claudian lineage. Load-bearing for [D-39](process.md#d-39-planning-tooling-lives-in-pql). -### D-004: Ignore file strategy +### D-4: Ignore file strategy - **Date:** 2026-04-20 (was ADR 0004; ported from the claudian lineage) -- **Decision:** One mechanism everywhere: the `ignore_files:` list in `.pql/config.yaml`. Ordered list of gitignore-shaped files; later entries win on per-pattern conflicts. pql defaults to `ignore_files: [.gitignore]`. Per [D-003](#d-003-pql-as-supporter-tool), clide writes the list on load — `[.gitignore, .clideignore]` if `.clideignore` exists, else `[.gitignore]`. `.clideignore` carries **only** the clide-specific deviations from `.gitignore` (supports `!pattern` negations); never duplicate gitignore's contents. Walker magic: none except `.git/` — every other tool-owned dir (`.pql/`, `.clide/`) is added to `.gitignore` at install time; exclusion flows through the normal `ignore_files:` chain. +- **Decision:** One mechanism everywhere: the `ignore_files:` list in `.pql/config.yaml`. Ordered list of gitignore-shaped files; later entries win on per-pattern conflicts. pql defaults to `ignore_files: [.gitignore]`. Per [D-3](#d-3-pql-as-supporter-tool), clide writes the list on load — `[.gitignore, .clideignore]` if `.clideignore` exists, else `[.gitignore]`. `.clideignore` carries **only** the clide-specific deviations from `.gitignore` (supports `!pattern` negations); never duplicate gitignore's contents. Walker magic: none except `.git/` — every other tool-owned dir (`.pql/`, `.clide/`) is added to `.gitignore` at install time; exclusion flows through the normal `ignore_files:` chain. - **Context:** Every file-enumerating surface in clide (pql query panels, canvas drivers, graph view, file watchers, pane lists, file tree) needs to skip the obvious junk — `vendor/`, `node_modules/`, `dist/`, build artifacts — or results drown in noise. Clide's working assumption is that the git repo *is* the workspace — no separate "vault" concept. - **Rationale:** Users get one config knob, in a file they might already know (pql users) or never need to touch (clide-only users). `.clideignore` is short by design — it's deltas, not a full list. Sidecar consumers read the same key and apply identical precedence, so Claude and the user always see the same filtered surface. - **Cost:** Removing clide from a repo leaves pql working with vanilla defaults (clide's last-written `ignore_files:` stays until pql or the user rewrites it; worth reconsidering during uninstall design). - **Raised by:** Ported from the claudian lineage. -### D-005: Dart core; sidecar dissolved; `ptyc` as pql-peer -- **Date:** 2026-04-20 (was ADR 0005; supersedes [R-002](rejected.md#r-002-go-sidecar)) -- **Amendment (2026-04-23):** The separate daemon process and two-package layout are dissolved per [D-056](#d-056-dissolve-daemon-process-flutter-app-hosts-ipc-server). Dart-core and ptyc-as-peer principles survive; the daemon binary does not. +### D-5: Dart core; sidecar dissolved; `ptyc` as pql-peer +- **Date:** 2026-04-20 (was ADR 0005; supersedes [R-2](rejected.md#r-2-go-sidecar)) +- **Amendment (2026-04-23):** The separate daemon process and two-package layout are dissolved per [D-56](#d-56-dissolve-daemon-process-flutter-app-hosts-ipc-server). Dart-core and ptyc-as-peer principles survive; the daemon binary does not. - **Decision:** Three moves. **(1) Dart is the core language.** Everything that used to live under `sidecar/` — IPC server, CLI dispatch, process management, file watching, git shell-outs, pql wrapper — is written in Dart. Two execution modes of one Dart AOT binary: `clide ` (one-shot, pql-style) and `clide --daemon` (long-running, owns PTYs and subprocesses, survives app restarts). The Flutter app imports the Dart core as a library *and* connects to the daemon over IPC. **(2) The sidecar directory dissolves.** Layout is `app/` (Flutter UI), `lib/` (Dart core), `bin/clide.dart` (AOT entry), `ptyc/` (C helper), no `sidecar/`, no Go module. **(3) `ptyc` is a pql-peer supporter tool.** Small C binary that does `posix_openpt` + `fork` + `exec` + fd-passing via `SCM_RIGHTS`; clide wraps it the same way it wraps pql. Shells out for every PTY (terminal pane, tmux session, Claude, LSP server, debug adapter — one code path). Consumers other than clide can use `ptyc` standalone. -- **Context:** [R-002](rejected.md#r-002-go-sidecar) picked Go for the sidecar/CLI on two premises: (a) the heavy work belongs in a language separate from the UI layer, and (b) pql is Go so the muscle memory transfers. On reassessment, both premises broke: the "heavy work" is I/O-bound glue that `dart:io` covers cleanly — the real choice was **separate process vs shared language**, and separate-process is what matters. PTY is the one place Dart is genuinely weak (multi-threaded VM can't safely `fork()`), and once you accept a small native helper, *nothing else* needs to be in the same language. +- **Context:** [R-2](rejected.md#r-2-go-sidecar) picked Go for the sidecar/CLI on two premises: (a) the heavy work belongs in a language separate from the UI layer, and (b) pql is Go so the muscle memory transfers. On reassessment, both premises broke: the "heavy work" is I/O-bound glue that `dart:io` covers cleanly — the real choice was **separate process vs shared language**, and separate-process is what matters. PTY is the one place Dart is genuinely weak (multi-threaded VM can't safely `fork()`), and once you accept a small native helper, *nothing else* needs to be in the same language. - **Rationale:** One toolchain for the IDE proper (Flutter + Dart). C toolchain needed only to build `ptyc` — tiny, rarely-changing. Session persistence stays because PTY master fds live in the Dart daemon process, not the app. `ptyc` naming: **p** for *project* (parallel to pql's *project query language*), **ptyc** reads as both "PTY + child" (domain vocabulary) and "PTY + C" (implementation language). Usable from Dart, Python, Go, shell — anywhere a subprocess can be spawned and a fd received. - **Cost:** Rust remains an escape hatch, not a plan. If a Dart limit later forces a second native helper (file-watching at scale on macOS, a tree-sitter host, etc.), the precedent is: new native need → new supporter tool, peer of pql and `ptyc`. Never a second "core language." Supply-chain gates stay, shape changes — Go `govulncheck` removed, Dart advisories review + exact-pin stays, `ptyc` gets a "read the 150 lines" review checklist (see `make security`). - **Raised by:** 2026-04-20 reassessment. See also the `ptyc` naming note in the original ADR (read as Project Terminal Controller / PTY+C / PTY+child). -### D-006: CLI and event surface contract +### D-6: CLI and event surface contract - **Date:** 2026-04-20 (was ADR 0006) - **Decision:** The CLI is organised into **subsystems**. Each subsystem owns a noun, a set of verbs, and a set of events. The set is closed at any point in time (documented); growth is additive (new verbs, new events — never renaming existing ones without a version bump). Initial subsystems (by tier): `pane`, `tab`, `open`, `editor`, `panel`, `tree`, `git`, `pql`, `canvas`, `graph`, `theme`, `settings`, `project`. Two umbrella entry points sit outside any subsystem: `clide tail --events [--filter [:]]` and `clide status`. Command shape: `clide [...] [--flag ...] [-- argv...]`. Exit codes parity with pql (`0/1/2/3/4` + `64-78` sysexits reserved); diagnostic JSON on **stderr** on non-zero exit; stdout stays machine-parseable on success. Events are JSON objects, one per line, with `v`, `ts`, `type` (`.`), `subsystem`, `id`, and `payload`; binary payloads base64. Every state-changing command emits at least one event; read-only commands emit nothing. Replay buffer per subsystem (default depth 16) so late subscribers still see recent effects. Parity rule: every UI affordance has a matching CLI verb (or a follow-up task naming the verb); every CLI verb surfaces in the UI (or documents why it's Claude-only). -- **Context:** [D-001](#d-001-cli-first-not-mcp) established that Claude drives clide via a Bash CLI. That decided the *channel* — it did not define the *surface*. CLAUDE.md stated the rule colloquially ("every CLI subcommand has a UI affordance … if you add one side without the other, the feature is incomplete"); this record restates it as an implementable contract that satisfies user/Claude parity, daemon-as-authoritative-state, and pql-style ergonomics at once. -- **Rationale:** Surface is enumerable — adding a subsystem means adding a row and specifying verbs + events. Wire schema is versioned (`v: 1` starting point; compatibility breaks bump the major and land alongside a `pubspec.yaml` `schema_version:` bump — see [Q-005](questions-architecture.md#q-005-ipc-wire-format-stability)). Events are the only UI→app state channel; the Flutter app does not poll. Extensions inherit this — a Dart extension publishes a subsystem; the same registration pipeline exposes it to Claude via the CLI. -- **Cost:** Replay-buffer memory per subsystem (cheap — most emit seldom). Back-pressure on firehose streams ([Q-002](questions-architecture.md#q-002-back-pressure-on-event-streams)), authorisation granularity ([Q-001](questions-architecture.md#q-001-authorisation-granularity)), and event persistence ([Q-003](questions-architecture.md#q-003-event-persistence-audit-undo)) are all deferred until Tier 1 is in real use. +- **Context:** [D-1](#d-1-cli-first-not-mcp) established that Claude drives clide via a Bash CLI. That decided the *channel* — it did not define the *surface*. CLAUDE.md stated the rule colloquially ("every CLI subcommand has a UI affordance … if you add one side without the other, the feature is incomplete"); this record restates it as an implementable contract that satisfies user/Claude parity, daemon-as-authoritative-state, and pql-style ergonomics at once. +- **Rationale:** Surface is enumerable — adding a subsystem means adding a row and specifying verbs + events. Wire schema is versioned (`v: 1` starting point; compatibility breaks bump the major and land alongside a `pubspec.yaml` `schema_version:` bump — see [Q-5](questions-architecture.md#q-5-ipc-wire-format-stability)). Events are the only UI→app state channel; the Flutter app does not poll. Extensions inherit this — a Dart extension publishes a subsystem; the same registration pipeline exposes it to Claude via the CLI. +- **Cost:** Replay-buffer memory per subsystem (cheap — most emit seldom). Back-pressure on firehose streams ([Q-2](questions-architecture.md#q-2-back-pressure-on-event-streams)), authorisation granularity ([Q-1](questions-architecture.md#q-1-authorisation-granularity)), and event persistence ([Q-3](questions-architecture.md#q-3-event-persistence-audit-undo)) are all deferred until Tier 1 is in real use. - **Raised by:** 2026-04-20 planning. -### D-043: Design handoff — adopt token palettes, reject Material wrapper +### D-43: Design handoff — adopt token palettes, reject Material wrapper - **Date:** 2026-04-22 -- **Decision:** The claude.ai/design handoff (`docs/claude-design/`) delivers hi-fi mockups, interaction flows, a design system, and four theme palettes (clide, midnight, paper, terminal) as Dart files using `MaterialApp`/`ThemeData`. We adopt the colour tokens, layout annotations, typography direction, and syntax highlighting palettes. We reject the `MaterialApp` wrapper — tokens are translated into our existing YAML theme pipeline and `SurfaceTokens` (per [D-007](#d-007-app-root-is-bare-widgetsapp)). The design files stay in `docs/claude-design/` as reference; they are not runtime assets. +- **Decision:** The claude.ai/design handoff (`docs/claude-design/`) delivers hi-fi mockups, interaction flows, a design system, and four theme palettes (clide, midnight, paper, terminal) as Dart files using `MaterialApp`/`ThemeData`. We adopt the colour tokens, layout annotations, typography direction, and syntax highlighting palettes. We reject the `MaterialApp` wrapper — tokens are translated into our existing YAML theme pipeline and `SurfaceTokens` (per [D-7](#d-7-app-root-is-bare-widgetsapp)). The design files stay in `docs/claude-design/` as reference; they are not runtime assets. - **Rationale:** The design's value is in the palette + layout + component vocabulary, not in the delivery format. Material's `ThemeData` fights our bare-`WidgetsApp` + `CustomPaint` stance. Translating tokens preserves design intent without absorbing Material's widget opinions. - **Cost:** Manual translation of four theme files into YAML. Ongoing: any design refresh needs the same translation pass. -- **Cross-reference:** [D-007](#d-007-app-root-is-bare-widgetsapp), [D-009](#d-009-three-tier-theme-pipeline), [R-012](rejected.md#r-012-materialapp-wrapper-from-design-handoff). +- **Cross-reference:** [D-7](#d-7-app-root-is-bare-widgetsapp), [D-9](#d-9-three-tier-theme-pipeline), [R-12](rejected.md#r-12-materialapp-wrapper-from-design-handoff). - **Raised by:** 2026-04-22 design handoff review. -### D-044: Four bundled themes — clide, midnight, paper, terminal +### D-44: Four bundled themes — clide, midnight, paper, terminal - **Date:** 2026-04-22 - **Decision:** Ship four bundled themes replacing the single summer-night preset. `clide` (cool near-black + periwinkle, default), `midnight` (VS Code-adjacent muted dark), `paper` (drafting-sheet light), `terminal` (near-black + amber). All share the same semantic token names. Source palettes in `docs/claude-design/themes/`; runtime YAML under `lib/kernel/src/theme/themes/`. - **Rationale:** Summer-night was a placeholder carried from the legacy TUI. The design system delivers a coherent set of four that covers dark, muted-dark, light, and monochrome workflows. - **Cost:** Summer-night users lose their theme (acceptable — it was dev-only). Four YAML files to maintain. -- **Cross-reference:** [D-043](#d-043-design-handoff-adopt-token-palettes-reject-material-wrapper), [D-022](accessibility.md#d-022-wcag-aa-contrast-gate-on-bundled-themes). +- **Cross-reference:** [D-43](#d-43-design-handoff-adopt-token-palettes-reject-material-wrapper), [D-22](accessibility.md#d-22-wcag-aa-contrast-gate-on-bundled-themes). - **Raised by:** 2026-04-22 design handoff review. -### D-045: Syntax highlighting tokens in the theme pipeline +### D-45: Syntax highlighting tokens in the theme pipeline - **Date:** 2026-04-22 -- **Decision:** Add syntax-role colour tokens to `SurfaceTokens`: keyword, type, string, number, comment, method, punctuation. Each bundled theme defines these. The editor and diff views consume them; tree-sitter (when it lands per [Q-015](questions-architecture.md#q-015-editor-tab-full-lsp-vs-tree-sitter-only)) maps grammar scopes to these tokens. +- **Decision:** Add syntax-role colour tokens to `SurfaceTokens`: keyword, type, string, number, comment, method, punctuation. Each bundled theme defines these. The editor and diff views consume them; tree-sitter (when it lands per [Q-15](questions-architecture.md#q-15-editor-tab-full-lsp-vs-tree-sitter-only)) maps grammar scopes to these tokens. - **Rationale:** The design system ships syntax palettes per theme. Adding them now means the token surface is ready when syntax highlighting lands. - **Cost:** Seven new fields on `SurfaceTokens`. Default resolution falls back to semantic roles (keyword → accent, comment → textMuted, etc.) so themes that don't declare syntax tokens still compile. - **Raised by:** 2026-04-22 design handoff review. -### D-047: Interaction model — Claude-is-home layout +### D-47: Interaction model — Claude-is-home layout - **Date:** 2026-04-22 -- **Decision:** The prompt bar is pinned to a fixed Y-position in the middle column; every other surface makes room *around* Claude — never on top, never pushing the prompt off-Y. Three hard rules: (1) prompt bar Y-position is invariant across all states (open, collapsed, focus, editor, viewer); (2) the three bottom strips (left icon rail, app strip, right icon rail) align to one continuous horizontal line; (3) Claude is always the largest surface when present. The three-column layout from [D-011](#d-011-panel-manager-is-kernel-layout-is-data-three-column-is-a-preset) is refined: left = overview (tickets, decisions, files, git, PRs), middle = Claude (+ optional editor above), right = context (viewer, pql graph, links, images). Both side panels have a bottom icon rail for section switching; keyboard: `⌥1–5` (left), context-type switcher (right). +- **Decision:** The prompt bar is pinned to a fixed Y-position in the middle column; every other surface makes room *around* Claude — never on top, never pushing the prompt off-Y. Three hard rules: (1) prompt bar Y-position is invariant across all states (open, collapsed, focus, editor, viewer); (2) the three bottom strips (left icon rail, app strip, right icon rail) align to one continuous horizontal line; (3) Claude is always the largest surface when present. The three-column layout from [D-11](#d-11-panel-manager-is-kernel-layout-is-data-three-column-is-a-preset) is refined: left = overview (tickets, decisions, files, git, PRs), middle = Claude (+ optional editor above), right = context (viewer, pql graph, links, images). Both side panels have a bottom icon rail for section switching; keyboard: `⌥1–5` (left), context-type switcher (right). - **Rationale:** "Claude is home" means the prompt never moves, regardless of what opens or closes around it. Every layout mutation respects this invariant. The three-column refinement assigns purpose to columns rather than leaving them generic. -- **Cost:** The prompt bar invariant constrains future layout presets — any preset that repositions Claude must explicitly break this rule. Editor mode (see [D-049](#d-049-editor-mode-inline-above-claude-viewer-swap)) is the only case where another surface shares the middle column, and it opens *above* Claude rather than displacing it. -- **Cross-reference:** [D-011](#d-011-panel-manager-is-kernel-layout-is-data-three-column-is-a-preset), [D-048](#d-048-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first), [D-049](#d-049-editor-mode-inline-above-claude-viewer-swap). +- **Cost:** The prompt bar invariant constrains future layout presets — any preset that repositions Claude must explicitly break this rule. Editor mode (see [D-49](#d-49-editor-mode-inline-above-claude-viewer-swap)) is the only case where another surface shares the middle column, and it opens *above* Claude rather than displacing it. +- **Cross-reference:** [D-11](#d-11-panel-manager-is-kernel-layout-is-data-three-column-is-a-preset), [D-48](#d-48-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first), [D-49](#d-49-editor-mode-inline-above-claude-viewer-swap). - **Raised by:** 2026-04-22 interaction model spec (Wireframe — Flows v3). -### D-048: Chrome budget — no tabs, no breadcrumbs, keyboard-first +### D-48: Chrome budget — no tabs, no breadcrumbs, keyboard-first - **Date:** 2026-04-22 -- **Decision:** Clide deletes classic IDE chrome: no buffer tabs, no breadcrumbs, no VS Code-style activity bar, no separate status bar row (merged into app strip). Total persistent chrome: 2 edge arrows (collapse toggles), 1 hover-only `⛶` glyph per panel (focus mode), 0 always-visible buttons beyond icon rails. `⌘P` overlay is the fuzzy finder — no layout shift. Keyboard is the primary interaction surface; icons are escape hatches. Files open individually; opening a second file closes the first (split on explicit command — deferred, see [Q-027](questions-architecture.md#q-027-two-editor-split)). -- **Rationale:** Every pixel of chrome that isn't Claude is a tax on the "Claude is home" principle. Tabs and breadcrumbs are navigation affordances for a multi-buffer editor; clide's editor is a secondary surface (viewer ↔ editor swap per [D-049](#d-049-editor-mode-inline-above-claude-viewer-swap)), not a primary one. The fuzzy finder (`⌘P`) replaces all navigation chrome. -- **Cost:** Users accustomed to VS Code/IntelliJ tab workflows have no tabs to fall back on. Mitigated by `⌘P` fuzzy find being the universal navigation path. Resolves T-022 (multi-buffer editor tabs) as rejected in favour of this approach. -- **Cross-reference:** [D-047](#d-047-interaction-model-claude-is-home-layout), [D-049](#d-049-editor-mode-inline-above-claude-viewer-swap). +- **Decision:** Clide deletes classic IDE chrome: no buffer tabs, no breadcrumbs, no VS Code-style activity bar, no separate status bar row (merged into app strip). Total persistent chrome: 2 edge arrows (collapse toggles), 1 hover-only `⛶` glyph per panel (focus mode), 0 always-visible buttons beyond icon rails. `⌘P` overlay is the fuzzy finder — no layout shift. Keyboard is the primary interaction surface; icons are escape hatches. Files open individually; opening a second file closes the first (split on explicit command — deferred, see [Q-27](questions-architecture.md#q-27-two-editor-split)). +- **Rationale:** Every pixel of chrome that isn't Claude is a tax on the "Claude is home" principle. Tabs and breadcrumbs are navigation affordances for a multi-buffer editor; clide's editor is a secondary surface (viewer ↔ editor swap per [D-49](#d-49-editor-mode-inline-above-claude-viewer-swap)), not a primary one. The fuzzy finder (`⌘P`) replaces all navigation chrome. +- **Cost:** Users accustomed to VS Code/IntelliJ tab workflows have no tabs to fall back on. Mitigated by `⌘P` fuzzy find being the universal navigation path. Resolves T-22 (multi-buffer editor tabs) as rejected in favour of this approach. +- **Cross-reference:** [D-47](#d-47-interaction-model-claude-is-home-layout), [D-49](#d-49-editor-mode-inline-above-claude-viewer-swap). - **Raised by:** 2026-04-22 interaction model spec (Wireframe — Flows v3). -### D-049: Editor mode — inline above Claude, viewer swap +### D-49: Editor mode — inline above Claude, viewer swap - **Date:** 2026-04-22 - **Decision:** Editor invoked via `⌘E` on a file or `✎` icon in a viewer. Editor lifts *above* Claude in the middle column, occupying 30–40% of vertical space; Claude keeps the remainder; prompt bar Y unchanged. Close with `⌘W`. Draggable divider between editor and Claude. The viewer (`👁`) and editor (`✎`) are mutually exclusive for the same file — a `✎` click on a viewer promotes the file to editor in the middle column and snaps the right panel back to nav; a `👁` click on an editor demotes the file to viewer in the right panel and closes the editor. Different files can coexist (editor on `main.dart` + viewer on `README.md`). When editor is open on `.md`, the viewer auto-opens with live sync to editor content; no auto-viewer for non-renderable files (`.dart`, `.yaml`, etc.). -- **Rationale:** The editor is not a primary surface — it's a temporary intervention. Claude's prompt bar must never move ([D-047](#d-047-interaction-model-claude-is-home-layout)), so the editor opens above, not replacing. The viewer ↔ editor swap prevents two surfaces showing the same file simultaneously, which simplifies state management and avoids confusion about which surface is authoritative. -- **Cost:** Only one file in the editor at a time (no tabs per [D-048](#d-048-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first)). Power users wanting two files side-by-side must wait for split (see [Q-027](questions-architecture.md#q-027-two-editor-split)). -- **Cross-reference:** [D-047](#d-047-interaction-model-claude-is-home-layout), [D-048](#d-048-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first). +- **Rationale:** The editor is not a primary surface — it's a temporary intervention. Claude's prompt bar must never move ([D-47](#d-47-interaction-model-claude-is-home-layout)), so the editor opens above, not replacing. The viewer ↔ editor swap prevents two surfaces showing the same file simultaneously, which simplifies state management and avoids confusion about which surface is authoritative. +- **Cost:** Only one file in the editor at a time (no tabs per [D-48](#d-48-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first)). Power users wanting two files side-by-side must wait for split (see [Q-27](questions-architecture.md#q-27-two-editor-split)). +- **Cross-reference:** [D-47](#d-47-interaction-model-claude-is-home-layout), [D-48](#d-48-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first). - **Raised by:** 2026-04-22 interaction model spec (Wireframe — Flows v3). -### D-050: Context auto-behavior — right panel reacts to Claude +### D-50: Context auto-behavior — right panel reacts to Claude - **Date:** 2026-04-22 - **Decision:** The right panel responds to Claude's content references automatically: (1) right open + empty → panel holds footprint, stays empty; (2) right open + viewer loaded + Claude links `foo.md` → swap in, replaces current viewer; (3) right collapsed + Claude links `foo.md` → badge on spine ("2"), no layout shift; (4) editor open on `.md` → viewer auto-opens with live sync; (5) editor on non-renderable file → no auto-viewer. - **Rationale:** Claude is the driver; the context panel is reactive. Auto-swapping when the panel is open reduces user clicks. Badging when collapsed respects the user's decision to collapse — no involuntary layout shifts. - **Cost:** The auto-swap requires the daemon (or Claude integration) to emit structured content references, not just terminal text. This implies a lightweight parser or event that identifies file references in Claude's output — deferred to implementation. -- **Cross-reference:** [D-047](#d-047-interaction-model-claude-is-home-layout), [D-051](#d-051-panel-collapse-12px-spine-with-badge). +- **Cross-reference:** [D-47](#d-47-interaction-model-claude-is-home-layout), [D-51](#d-51-panel-collapse-12px-spine-with-badge). - **Raised by:** 2026-04-22 interaction model spec (Wireframe — Flows v3). -### D-051: Panel collapse — 12px spine with badge +### D-51: Panel collapse — 12px spine with badge - **Date:** 2026-04-22 - **Decision:** When collapsed, a panel becomes a 12px spine: vertically rotated label ("tickets" / "context"), no icon rail, `paper-2` background (slightly darker than main paper), border on inner edge only. Click anywhere on spine to expand. If a context badge is pending (e.g. Claude linked a file while collapsed): small filled dot with count at top of spine. Edge arrow on outer boundary toggles collapse; keyboard: `⌘⇧1` (left) / `⌘⇧3` (right). Expand restores prior size and section state. - **Rationale:** Collapsed panels must not consume significant horizontal space (12px = 1 icon-width) but must remain discoverable and able to signal pending content. The badge-on-spine avoids involuntary expand while still communicating that something arrived. -- **Cost:** The spine replaces the current simple `setVisible(false)` toggle with a real collapsed-state widget. Collapse state must be persisted across sessions (see [D-053](#d-053-state-persistence-across-sessions)). -- **Cross-reference:** [D-047](#d-047-interaction-model-claude-is-home-layout), [D-050](#d-050-context-auto-behavior-right-panel-reacts-to-claude). +- **Cost:** The spine replaces the current simple `setVisible(false)` toggle with a real collapsed-state widget. Collapse state must be persisted across sessions (see [D-53](#d-53-state-persistence-across-sessions)). +- **Cross-reference:** [D-47](#d-47-interaction-model-claude-is-home-layout), [D-50](#d-50-context-auto-behavior-right-panel-reacts-to-claude). - **Raised by:** 2026-04-22 interaction model spec (Wireframe — Flows v3). -### D-052: Focus mode — full-window takeover +### D-52: Focus mode — full-window takeover - **Date:** 2026-04-22 - **Decision:** Focus mode entered via double-click on panel header, hover-visible `⛶` glyph in header, or `⌘.`. Active panel takes the full window; all others hidden. Header shows "Esc" hint. `Esc` restores the exact prior layout (collapse state, divider positions, active sections). Focus mode is per-panel, not per-tab. - **Rationale:** When the user wants to concentrate on a single surface — Claude conversation, file tree, diff view — they shouldn't have to manually collapse both side panels. Focus mode is a single-action "maximise and restore" with no state loss. - **Cost:** Must snapshot and restore full `LayoutArrangement` state on enter/exit. Interacts with responsive behaviour — focus mode at narrow widths should work identically. -- **Cross-reference:** [D-047](#d-047-interaction-model-claude-is-home-layout), [D-053](#d-053-state-persistence-across-sessions). +- **Cross-reference:** [D-47](#d-47-interaction-model-claude-is-home-layout), [D-53](#d-53-state-persistence-across-sessions). - **Raised by:** 2026-04-22 interaction model spec (Wireframe — Flows v3). -### D-053: State persistence across sessions +### D-53: State persistence across sessions - **Date:** 2026-04-22 - **Decision:** The following layout state is persisted across app restarts: collapse state of left and right panels, active left section (tickets/decisions/files/git/pr), active right context type, pql pane expanded/collapsed, editor split ratio when open, fuzzy find recent picks. Stored via `SettingsStore` in project-scoped settings (`.clide/settings.yaml`). - **Rationale:** Users expect their workspace layout to survive restarts. Without persistence, every launch starts at the default layout preset, which is disorienting when the user has customised their column widths and panel states. - **Cost:** Adds write-on-change to several layout operations. Must handle migration if the setting keys evolve. `.clide/settings.yaml` is already gitignored, so personal layout state stays personal. -- **Cross-reference:** [D-047](#d-047-interaction-model-claude-is-home-layout), [D-051](#d-051-panel-collapse-12px-spine-with-badge), [D-052](#d-052-focus-mode-full-window-takeover). +- **Cross-reference:** [D-47](#d-47-interaction-model-claude-is-home-layout), [D-51](#d-51-panel-collapse-12px-spine-with-badge), [D-52](#d-52-focus-mode-full-window-takeover). - **Raised by:** 2026-04-22 interaction model spec (Wireframe — Flows v3). -### D-054: Keyboard map — canonical shortcuts +### D-54: Keyboard map — canonical shortcuts - **Date:** 2026-04-22 -- **Decision:** Canonical keyboard shortcuts (cross-platform, `⌘` = `Ctrl` on Linux): `⌘P` fuzzy find overlay; `⌘⇧1` / `⌘⇧3` collapse/expand left / right panel; `⌘1` / `⌘2` / `⌘3` focus left / middle / right panel; `⌘.` toggle focus mode on focused panel; `⌥1–⌥5` left-panel section switch (tickets, decisions, files, git, pr); `⌘E` open current file in editor; `⌘W` close editor / dismiss viewer; `Esc` exit focus mode / close fuzzy finder / dismiss viewer. Responsive breakpoints: ≥ 1600px splits relax toward 30%; 1200–1600px default (L 200px, R 220px, middle flex); < 1200px splits toward 40%, consider auto-collapse right; < 1000px deferred (see [Q-026](questions-architecture.md#q-026-small-screen-layout)). -- **Rationale:** These shortcuts follow the "keyboard is the primary surface" principle from [D-048](#d-048-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first). The set is minimal and covers all layout operations. `⌘.` for focus mode follows VS Code precedent (quick-fix → general "do the thing"). -- **Cost:** Some shortcuts may conflict with OS-level bindings on specific Linux desktops; the keybinding resolver ([D-017](extensions.md#d-017-panels-are-extension-shaped-from-day-one)) allows user override. -- **Cross-reference:** [D-047](#d-047-interaction-model-claude-is-home-layout), [D-048](#d-048-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first), [D-052](#d-052-focus-mode-full-window-takeover). +- **Decision:** Canonical keyboard shortcuts (cross-platform, `⌘` = `Ctrl` on Linux): `⌘P` fuzzy find overlay; `⌘⇧1` / `⌘⇧3` collapse/expand left / right panel; `⌘1` / `⌘2` / `⌘3` focus left / middle / right panel; `⌘.` toggle focus mode on focused panel; `⌥1–⌥5` left-panel section switch (tickets, decisions, files, git, pr); `⌘E` open current file in editor; `⌘W` close editor / dismiss viewer; `Esc` exit focus mode / close fuzzy finder / dismiss viewer. Responsive breakpoints: ≥ 1600px splits relax toward 30%; 1200–1600px default (L 200px, R 220px, middle flex); < 1200px splits toward 40%, consider auto-collapse right; < 1000px deferred (see [Q-26](questions-architecture.md#q-26-small-screen-layout)). +- **Rationale:** These shortcuts follow the "keyboard is the primary surface" principle from [D-48](#d-48-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first). The set is minimal and covers all layout operations. `⌘.` for focus mode follows VS Code precedent (quick-fix → general "do the thing"). +- **Cost:** Some shortcuts may conflict with OS-level bindings on specific Linux desktops; the keybinding resolver ([D-17](extensions.md#d-17-panels-are-extension-shaped-from-day-one)) allows user override. +- **Cross-reference:** [D-47](#d-47-interaction-model-claude-is-home-layout), [D-48](#d-48-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first), [D-52](#d-52-focus-mode-full-window-takeover). - **Raised by:** 2026-04-22 interaction model spec (Wireframe — Flows v3). -### D-055: Claude pane internal tabs for multi-session +### D-55: Claude pane internal tabs for multi-session - **Date:** 2026-04-23 -- **Decision:** Multiple Claude sessions share the workspace as internal tabs inside the Claude pane header — not as workspace-level tabs (which would violate [D-048](#d-048-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first)). The primary session tab has no close affordance (per [D-041](#d-041-claude-panes-one-primary-per-repo-tmux-backed)). Secondary session tabs show a close `×`. A small `+` button sits at the right end of the tab row to spawn a new secondary. Double-clicking empty space in the tab row also spawns a new secondary. When a secondary is closed, focus collapses to the most-recently-active remaining tab (primary or another secondary). The tab row is hidden when only the primary exists — it appears on first secondary spawn and disappears when the last secondary closes. Session names in the tab row use the tmux session name slug (readable path, per the session naming convention). -- **Amendment to [D-041](#d-041-claude-panes-one-primary-per-repo-tmux-backed):** D-041 defined the lifecycle (primary persists, secondaries are ephemeral, close semantics) but left the multi-session UI unspecified. This record fills that gap. The `claude.new-secondary` command (already registered but not wired) is the spawn mechanism; the tab row is the UI surface. -- **Rationale:** The workspace is Claude's space ([D-047](#d-047-interaction-model-claude-is-home-layout)). Multiple Claude sessions are a Claude concern, not a workspace concern. Internal tabs keep the multiplicity contained — the workspace slot doesn't know how many sessions exist, it just renders the Claude pane. The hide-when-one rule keeps the common case (single primary) chrome-free. +- **Decision:** Multiple Claude sessions share the workspace as internal tabs inside the Claude pane header — not as workspace-level tabs (which would violate [D-48](#d-48-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first)). The primary session tab has no close affordance (per [D-41](#d-41-claude-panes-one-primary-per-repo-tmux-backed)). Secondary session tabs show a close `×`. A small `+` button sits at the right end of the tab row to spawn a new secondary. Double-clicking empty space in the tab row also spawns a new secondary. When a secondary is closed, focus collapses to the most-recently-active remaining tab (primary or another secondary). The tab row is hidden when only the primary exists — it appears on first secondary spawn and disappears when the last secondary closes. Session names in the tab row use the tmux session name slug (readable path, per the session naming convention). +- **Amendment to [D-41](#d-41-claude-panes-one-primary-per-repo-tmux-backed):** D-41 defined the lifecycle (primary persists, secondaries are ephemeral, close semantics) but left the multi-session UI unspecified. This record fills that gap. The `claude.new-secondary` command (already registered but not wired) is the spawn mechanism; the tab row is the UI surface. +- **Rationale:** The workspace is Claude's space ([D-47](#d-47-interaction-model-claude-is-home-layout)). Multiple Claude sessions are a Claude concern, not a workspace concern. Internal tabs keep the multiplicity contained — the workspace slot doesn't know how many sessions exist, it just renders the Claude pane. The hide-when-one rule keeps the common case (single primary) chrome-free. - **Cost:** The Claude pane grows its own tab model (lightweight — just a list of session IDs + which is active). The `builtin.claude` extension owns this; no kernel changes needed. -- **Cross-reference:** [D-041](#d-041-claude-panes-one-primary-per-repo-tmux-backed), [D-047](#d-047-interaction-model-claude-is-home-layout), [D-048](#d-048-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first). +- **Cross-reference:** [D-41](#d-41-claude-panes-one-primary-per-repo-tmux-backed), [D-47](#d-47-interaction-model-claude-is-home-layout), [D-48](#d-48-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first). - **Raised by:** 2026-04-23 interaction model refinement. -### D-056: Dissolve daemon process; Flutter app hosts IPC server +### D-56: Dissolve daemon process; Flutter app hosts IPC server - **Date:** 2026-04-23 -- **Decision:** The separate Dart daemon process (`clide --daemon`) and the two-package repo layout (`lib/` core + `app/` Flutter) are dissolved. The Flutter app moves to the repo root (one `pubspec.yaml`) and hosts the IPC server in-process. All subsystem handlers (pane, files, editor, git, pql) run inside the Flutter process. The `bin/clide.dart` AOT binary is removed. The CLI surface for Claude (`clide `) becomes a thin C client — either a new peer of `ptyc` or a mode within `ptyc` itself — that connects to the app's unix socket, sends a JSON-lines request, prints the response, and exits. tmux owns session persistence (it already did per [D-041](#d-041-claude-panes-one-primary-per-repo-tmux-backed)); the daemon's PTY ownership was redundant. -- **Amendment to [D-005](#d-005-dart-core-sidecar-dissolved-ptyc-as-pql-peer):** D-005's "two execution modes of one Dart AOT binary" premise assumed the daemon needed to outlive the app to preserve PTY sessions. tmux already solves this — `tmux new-session -A` re-attaches regardless of which process originally spawned it. The daemon process added complexity (two packages, two build targets, IPC client/server split, process lifecycle management) without a benefit tmux doesn't already provide. D-005's other principles survive: Dart is the core language, `ptyc` is a C peer of pql, one language for the IDE proper. +- **Decision:** The separate Dart daemon process (`clide --daemon`) and the two-package repo layout (`lib/` core + `app/` Flutter) are dissolved. The Flutter app moves to the repo root (one `pubspec.yaml`) and hosts the IPC server in-process. All subsystem handlers (pane, files, editor, git, pql) run inside the Flutter process. The `bin/clide.dart` AOT binary is removed. The CLI surface for Claude (`clide `) becomes a thin C client — either a new peer of `ptyc` or a mode within `ptyc` itself — that connects to the app's unix socket, sends a JSON-lines request, prints the response, and exits. tmux owns session persistence (it already did per [D-41](#d-41-claude-panes-one-primary-per-repo-tmux-backed)); the daemon's PTY ownership was redundant. +- **Amendment to [D-5](#d-5-dart-core-sidecar-dissolved-ptyc-as-pql-peer):** D-5's "two execution modes of one Dart AOT binary" premise assumed the daemon needed to outlive the app to preserve PTY sessions. tmux already solves this — `tmux new-session -A` re-attaches regardless of which process originally spawned it. The daemon process added complexity (two packages, two build targets, IPC client/server split, process lifecycle management) without a benefit tmux doesn't already provide. D-5's other principles survive: Dart is the core language, `ptyc` is a C peer of pql, one language for the IDE proper. - **Repo layout after dissolution:** - `/pubspec.yaml` — single Flutter package (was `app/pubspec.yaml`) - `/lib/` — all Dart code: kernel, extensions, widgets, subsystem handlers @@ -223,16 +223,16 @@ Core, rendering, IPC, kernel, panel manager. - `/decisions/`, `/docs/`, `/legacy/` — unchanged - **Rationale:** One package means one `pubspec.yaml`, one `flutter analyze`, one `flutter test`, no `cd` gymnastics, no cross-package import barriers. The IPC server running in-process eliminates the daemon lifecycle (start, stop, reconnect, pid file). If the app crashes, tmux sessions survive; the app re-attaches on restart. The CLI client in C is ~100 lines (socket connect + JSON exchange) with the same contract as pql. - **Cost:** If the Flutter app is not running, Claude's `clide` commands fail. In practice this is acceptable — the IDE being closed means the user isn't working. A future "headless mode" could start the Flutter engine without a window if needed. -- **Cross-reference:** [D-005](#d-005-dart-core-sidecar-dissolved-ptyc-as-pql-peer) (amended), [D-041](#d-041-claude-panes-one-primary-per-repo-tmux-backed) (tmux persistence), [D-001](#d-001-cli-first-not-mcp) (CLI-first surface preserved via C client). +- **Cross-reference:** [D-5](#d-5-dart-core-sidecar-dissolved-ptyc-as-pql-peer) (amended), [D-41](#d-41-claude-panes-one-primary-per-repo-tmux-backed) (tmux persistence), [D-1](#d-1-cli-first-not-mcp) (CLI-first surface preserved via C client). - **Raised by:** 2026-04-23 architectural simplification. -### D-057: Frameless custom chrome with per-column 24px hats +### D-57: Frameless custom chrome with per-column 24px hats - **Date:** 2026-04-23 - **Decision:** The OS-native title bar is hidden. Each of the three columns wears its own 24px "hat" that serves as both a drag region and a host for window controls. Left hat: macOS traffic lights (Linux/Windows: plain drag). Center hat: `clide > branch` label, always present. Right hat: minimize/maximize/close glyph buttons on Linux/Windows (macOS: plain drag). Entire hat surface is draggable; buttons opt out of hit testing. When a column collapses to a 12px spine, its hat shrinks to a 12px drag cap — no buttons, still draggable. The center hat never collapses. Three `ChromeStyle` variants: `seam` (default desktop — full hats), `prompt` (center hat only — presentations/focus), `inline` (web/wasm — no hats, browser owns window controls). Persisted in settings as `app.chromeStyle`. Platform bridge via `MethodChannel('clide/window')` — custom GTK C and Cocoa Swift handlers, no third-party package. -- **Resolves:** [Q-006](questions-architecture.md#q-006-window-chrome-native-frame-vs-frameless-custom). -- **Rationale:** The GTK headerbar wastes 30+ vertical pixels and clashes with the custom theme. Per-column hats add zero net rows — they reuse the space each column header already occupied. Custom FFI avoids a `window_manager` dependency (D-031). The `ChromeStyle` enum keeps web builds clean and allows user override. +- **Resolves:** [Q-6](questions-architecture.md#q-6-window-chrome-native-frame-vs-frameless-custom). +- **Rationale:** The GTK headerbar wastes 30+ vertical pixels and clashes with the custom theme. Per-column hats add zero net rows — they reuse the space each column header already occupied. Custom FFI avoids a `window_manager` dependency (D-31). The `ChromeStyle` enum keeps web builds clean and allows user override. - **Cost:** ~150 lines C (GTK) + ~100 lines Swift (Cocoa) for the platform channel. Window controls become unreachable when their column collapses — mitigated by keyboard shortcuts (`⌘Q` to close, `⌘1`/`⌘3` to expand). -- **Cross-reference:** [D-047](#d-047-interaction-model-claude-is-home-layout) (center hat always visible), [D-051](#d-051-panel-collapse-12px-spine-with-badge) (spine-cap behavior). +- **Cross-reference:** [D-47](#d-47-interaction-model-claude-is-home-layout) (center hat always visible), [D-51](#d-51-panel-collapse-12px-spine-with-badge) (spine-cap behavior). - **Raised by:** 2026-04-23 interaction model refinement. --- diff --git a/decisions/extensions.md b/decisions/extensions.md index 8cd8decf..0ac9f373 100644 --- a/decisions/extensions.md +++ b/decisions/extensions.md @@ -4,42 +4,42 @@ Extension contract, Lua runtime, grain, contribution points. --- -### D-015: Extension grain — container-level, multi-contribution +### D-15: Extension grain — container-level, multi-contribution - **Date:** 2026-04-21 - **Decision:** An extension is a shipping unit that contributes one or more named contributions (panel, command, theme, keybinding, language, layout, provider, view). Grain is container-sized — a single `builtin.git` extension contributes panel + commands + keybindings + status-bar items; we do not ship one extension per contribution. - **Rationale:** Finer grain (one extension per contribution) multiplies manifest files with no win and fragments ownership. Coarser grain (one mega-extension per domain) hides which parts a user might reasonably disable. - **Cost:** Extension authors make a taste call about grouping; disagreements go to review. - **Raised by:** 2026-04-21 planning. -### D-016: Built-ins in Dart, third-party in sandboxed Lua +### D-16: Built-ins in Dart, third-party in sandboxed Lua - **Date:** 2026-04-21 -- **Decision:** Bundled extensions (every `app/lib/builtin/`) are Dart — they link into the app binary. Third-party extensions (Tier 6) run in sandboxed Lua via the `ptyc`-peer Lua runtime (see [D-019](#d-019-lua-runtime-as-ptyc-peer-supporter-tool)). The contribution contract is language-agnostic — same contribution shapes, same manifest schema. +- **Decision:** Bundled extensions (every `app/lib/builtin/`) are Dart — they link into the app binary. Third-party extensions (Tier 6) run in sandboxed Lua via the `ptyc`-peer Lua runtime (see [D-19](#d-19-lua-runtime-as-ptyc-peer-supporter-tool)). The contribution contract is language-agnostic — same contribution shapes, same manifest schema. - **Rationale:** Dart built-ins get full SDK power (custom painters, isolates, FFI); third-party Lua gets a narrow capability API, no arbitrary syscalls, no deps on pub.dev. VS Code's Node-runs-with-full-power model is a supply-chain nightmare we're explicitly rejecting. - **Cost:** Two implementation paths for the same contract; we pay in API design to keep them equivalent at the seams. - **Raised by:** 2026-04-21 planning. -### D-017: Panels are extension-shaped from day one +### D-17: Panels are extension-shaped from day one - **Date:** 2026-04-21 - **Decision:** Every bundled panel (file tree, git, problems, pql query, terminal, etc.) is a contribution on the extension contract, not a hardcoded widget tree in the panel manager. First party contributes via Dart; third party contributes via Lua; same contract. - **Rationale:** Forces the contract to be real on day one. The alternative ("extensions can contribute panels *later*") always becomes "the contract doesn't quite fit our bundled panels, so built-ins get a shortcut" — and the shortcut becomes permanent. - **Cost:** Every panel goes through the manifest/registration path even when it's trivial. Price paid once. - **Raised by:** 2026-04-21 planning. -### D-018: YAML for themes + manifests; JSON for i18n catalogs +### D-18: YAML for themes + manifests; JSON for i18n catalogs - **Date:** 2026-04-21 -- **Decision:** Themes and extension manifests are YAML; i18n catalogues are JSON (fframe parity — see [D-021](accessibility.md#d-021-i18n-is-a-tier-0-contract)). +- **Decision:** Themes and extension manifests are YAML; i18n catalogues are JSON (fframe parity — see [D-21](accessibility.md#d-21-i18n-is-a-tier-0-contract)). - **Rationale:** YAML for human-edited config files (themes, manifests) — comments, multi-line strings, less noise. JSON for machine-written / machine-read files (i18n catalogs get generated by translation tooling eventually). Mixing is fine; each format is where it's best. - **Cost:** Two parsers in the tree. `yaml: 3.1.3` is exact-pinned. - **Raised by:** 2026-04-21 planning. -### D-019: Lua runtime as `ptyc`-peer supporter tool +### D-19: Lua runtime as `ptyc`-peer supporter tool - **Date:** 2026-04-21 - **Decision:** Third-party extensions run Lua inside a sandboxed runtime (working name TBD; same peer-status as `ptyc` and `pql`). The runtime vendors liblua, links from Dart via `dart:ffi`, exposes a narrow capability API, and takes contributions as a declarative render-intent DSL (widget shapes, not arbitrary widget code). - **Rationale:** Lua is small, embeddable, battle-tested (Neovim, World of Warcraft, Redis). Native sandboxing at the VM level is cheap. A declarative render-intent DSL keeps "run arbitrary Flutter widgets from Lua" off the table — the runtime interprets the DSL into Dart widgets, which keeps Tier 6 supply-chain risk bounded. - **Cost:** Runtime is a separate supporter tool to build; ffi is tricky. Deferred to Tier 6; only the slot is reserved now. - **Raised by:** 2026-04-21 planning. -### D-046: Core frame builtins vs shipped extensions boundary +### D-46: Core frame builtins vs shipped extensions boundary - **Date:** 2026-04-22 - **Decision:** The `app/lib/builtin/` directory is reserved for core frame infrastructure — components the shell cannot function without. Everything that renders *content* (editor surfaces, tool panels, integrations) is a shipped extension: still Dart, still bundled in the binary, but architecturally an extension that registers through the contribution contract and could in principle be disabled by the user. @@ -60,5 +60,5 @@ Extension contract, Lua runtime, grain, contribution points. --- *See also the existing `builtin.grammars_core` stub for tree-sitter -questions ([Q-015](questions-process.md#q-015-editor-tab-full-lsp-vs-tree-sitter-only), -[Q-016](questions-process.md#q-016-tree-sitter-dart-grammar-maintenance)).* +questions ([Q-15](questions-process.md#q-15-editor-tab-full-lsp-vs-tree-sitter-only), +[Q-16](questions-process.md#q-16-tree-sitter-dart-grammar-maintenance)).* diff --git a/decisions/process.md b/decisions/process.md index 48ea514a..c1e48d1a 100644 --- a/decisions/process.md +++ b/decisions/process.md @@ -4,55 +4,55 @@ Q&D record system itself, kanban, commit conventions, changelog. --- -### D-034: Q&D record system +### D-34: Q&D record system - **Date:** 2026-04-21 - **Decision:** Adopt settled-reach's Q&D record convention. Confirmed decisions are `D-NNN` under `decisions/.md`; open questions are `Q-NNN` under `decisions/questions-.md`; rejected alternatives are `R-NNN` under `decisions/rejected.md`. Markdown is the source of truth; `.pql/pql.db` is a query index built from markdown. Record shape and claiming rules live in [`decisions/README.md`](README.md). - **Rationale:** Two places currently hold clide's architectural knowledge — ADRs and scattered plan files — and neither lets an agent or reviewer locate "the unresolved thing in this subsystem." Q&D fixes that: one index, one shape, one claim rule. Proven in daily use in settled-reach. - **Cost:** One more directory to maintain. A learning curve for contributors (tiny: read `decisions/README.md`). - **Raised by:** 2026-04-21 planning. -### D-035: Kanban / waterfall, not Scrum +### D-35: Kanban / waterfall, not Scrum - **Date:** 2026-04-21 - **Decision:** Ticketing is kanban + waterfall. Tickets flow backlog → ready → in_progress → review → done → cancelled. No sprints, no velocity, no story points. Settled-reach's Scrum layer (sprints, sprint reviews, sprint close as a sync event) is stripped. - **Rationale:** Clide has a solo-or-small-team cadence. Sprint ceremonies add overhead without adding signal at this scale. Kanban matches how the work actually happens. -- **Cost:** No natural "sprint close" event to sync shared state. See [Q-022](questions-process.md#q-022-ticket-persistence-strategy). +- **Cost:** No natural "sprint close" event to sync shared state. See [Q-22](questions-process.md#q-22-ticket-persistence-strategy). - **Raised by:** 2026-04-21 planning. -### D-036: `.claude/` is committed project surface, managed through the IDE +### D-36: `.claude/` is committed project surface, managed through the IDE - **Date:** 2026-04-21 - **Decision:** `.claude/` (hooks, skills, agents, MCP settings) is committed alongside code. Only `.claude/settings.local.json` is gitignored. The reserved `builtin.claude-control` extension surfaces `.claude/` as a first-class sidebar tab (sub-tabs: Settings / Skills / Agents / Hooks / MCP) in a future tier. - **Rationale:** `.claude/` is project governance — same status as `CLAUDE.md`, `decisions/`, `Makefile`. Treating it as dotfile-cruft loses project-wide conventions (skills, hooks) that should travel with the repo. - **Cost:** Contributors commit Claude Code config alongside code changes. Discipline required; minor. - **Raised by:** 2026-04-21 planning. Distinct from the existing `builtin.claude` stub reserved for Tier 1's "run Claude Code in a PTY pane." -### D-037: Commit conventions per git-commit skill +### D-37: Commit conventions per git-commit skill - **Date:** 2026-04-21 - **Decision:** Commits follow `.claude/skills/git-commit/SKILL.md`: imperative subject ≤ 70 chars, no `feat:`/`fix:` type prefixes, no emojis, optional body wrapped at ~72 chars, multi-line messages via HEREDOC, attribution trailer `Co-Authored-By: Claude ` (the model-identifier variant the harness produces is also accepted). - **Rationale:** Python-era clide under `legacy/` used Conventional Commits; the Flutter rebuild does not. Imperative mood reads better for a project-governance log; types are noise when every commit is scoped to a subsystem already. - **Cost:** Contributors with Conventional Commits muscle memory adjust. - **Raised by:** 2026-04-21 planning. -### D-038: Changelog discipline — Keep a Changelog 1.1.0 +### D-38: Changelog discipline — Keep a Changelog 1.1.0 - **Date:** 2026-04-21 - **Decision:** `CHANGELOG.md` follows Keep a Changelog 1.1.0. Every user-visible commit adds an entry under `## [Unreleased]` in the appropriate subsection (Added / Changed / Deprecated / Removed / Fixed / Security). Cutting a release moves entries under a dated heading and bumps `pubspec.yaml` `version:` in the same commit. Pure bookkeeping commits (comment-only, .gitignore tweak, lint config) skip the changelog. - **Rationale:** Release notes that have to be written after the fact aren't written. Writing them per commit keeps the log honest. - **Cost:** One extra edit per user-visible commit; zero if the change is invisible. - **Raised by:** 2026-04-21 planning. -### D-039: Planning tooling lives in pql, not clide +### D-39: Planning tooling lives in pql, not clide - **Date:** 2026-04-21 -- **Decision:** Planning subcommands (`decisions`, `ticket`, `plan`) land in pql's repo long-term. Clide consumes them via shell-out, matching [D-003](architecture.md)'s wrap-don't-duplicate rule for pql. Clide does not grow Dart subcommands for planning. -- **Rationale:** A terminal user or a user in VS Code / JetBrains still needs Q&D access. Binding planning tooling to clide-the-Flutter-app would cut them off from their own work — see [R-009](rejected.md#r-009-port-planning-tooling-into-clide). pql is already the CLI, already universal, already wrapped by clide. -- **Cost:** Planning features don't ship until pql catches up. Mitigated by [D-040](#d-040-python-stopgap-under-toolsscriptsplan). Gated by [Q-021](questions-process.md#q-021-pql-absorbs-planning-vs-keeps-separate). +- **Decision:** Planning subcommands (`decisions`, `ticket`, `plan`) land in pql's repo long-term. Clide consumes them via shell-out, matching [D-3](architecture.md)'s wrap-don't-duplicate rule for pql. Clide does not grow Dart subcommands for planning. +- **Rationale:** A terminal user or a user in VS Code / JetBrains still needs Q&D access. Binding planning tooling to clide-the-Flutter-app would cut them off from their own work — see [R-9](rejected.md#r-9-port-planning-tooling-into-clide). pql is already the CLI, already universal, already wrapped by clide. +- **Cost:** Planning features don't ship until pql catches up. Mitigated by [D-40](#d-40-python-stopgap-under-toolsscriptsplan). Gated by [Q-21](questions-process.md#q-21-pql-absorbs-planning-vs-keeps-separate). - **Raised by:** 2026-04-21 planning. -### D-040: [SUPERSEDED] Python stopgap under `tools/scripts/plan` +### D-40: [SUPERSEDED] Python stopgap under `tools/scripts/plan` - **Date:** 2026-04-21 - **Decision:** A time-limited Python port of settled-reach's `decisions_sync.py` + `ticket` + `decision` scripts lives at `tools/scripts/plan` with support modules under `tools/scripts/planning/`. Writes to `.pql/pql.db` (gitignored). Ticket IDs are `T-NNN` (TEXT PK, reshape from settled-reach's integers). Same schema, same markdown, same verb shape as the eventual `pql` subcommands. -- **Sunset:** Delete the stopgap when pql ships `pql decisions sync | validate | list | show | claim | coverage` + `pql ticket new | list | show | status | assign | block | board` with feature parity, and reads the same `.pql/pql.db` file the stopgap wrote. Removal commit shape: [R-011](rejected.md#r-011-permanent-stopgap). +- **Sunset:** Delete the stopgap when pql ships `pql decisions sync | validate | list | show | claim | coverage` + `pql ticket new | list | show | status | assign | block | board` with feature parity, and reads the same `.pql/pql.db` file the stopgap wrote. Removal commit shape: [R-11](rejected.md#r-11-permanent-stopgap). - **Rationale:** Planning tooling must work day one. Pql's Go implementation won't land for at least a cycle or two. Without a stopgap, the convention lives on paper; with one, tickets + decisions are queryable from today. Same schema means migration is call-site find-replace (`tools/scripts/plan ` → `pql `), no data migration. -- **Cost:** Python dep on contributors' machines (already present on most Linux dists). One time-limited tool to maintain. See [R-010](rejected.md#r-010-python-script-stopgap-at-toolingdb) for why `tools/scripts/plan` and not `tooling/db/`. +- **Cost:** Python dep on contributors' machines (already present on most Linux dists). One time-limited tool to maintain. See [R-10](rejected.md#r-10-python-script-stopgap-at-toolingdb) for why `tools/scripts/plan` and not `tooling/db/`. - **Raised by:** 2026-04-21 planning. -- **Amendment (2026-04-22):** Sunset condition met. pql 1.0.0 ships full feature parity. Stopgap deleted per [R-011](rejected.md#r-011-permanent-stopgap). +- **Amendment (2026-04-22):** Sunset condition met. pql 1.0.0 ships full feature parity. Stopgap deleted per [R-11](rejected.md#r-11-permanent-stopgap). --- diff --git a/decisions/questions-accessibility.md b/decisions/questions-accessibility.md index 633f3d4d..96453592 100644 --- a/decisions/questions-accessibility.md +++ b/decisions/questions-accessibility.md @@ -2,15 +2,15 @@ --- -### Q-013: Web production-mode a11y +### Q-13: Web production-mode a11y - **Status:** Open - **Question:** In a Flutter web release build, is the semantics tree always on (what we need for Playwright and for end-user screen readers) or gated behind an accessibility toggle (Flutter's default)? - **Context:** Today the driver clicks `flt-semantics-placeholder` to activate. For user-facing builds we need semantics-always-on. - **Source:** 2026-04-21 planning. -### Q-014: i18n plurals / gender / date-format tooling +### Q-14: i18n plurals / gender / date-format tooling - **Status:** Open -- **Question:** fframe's pattern covers straight key→string lookup with variable interpolation. Plurals, gendered forms, and ICU-style date formatting aren't in scope there. Do we add them to the i18n facade, defer to a runtime library (violates [D-031](tooling.md#d-031-prefer-zero-deps-exact-pin)), or require catalogues to provide pre-formatted strings per count/gender? +- **Question:** fframe's pattern covers straight key→string lookup with variable interpolation. Plurals, gendered forms, and ICU-style date formatting aren't in scope there. Do we add them to the i18n facade, defer to a runtime library (violates [D-31](tooling.md#d-31-prefer-zero-deps-exact-pin)), or require catalogues to provide pre-formatted strings per count/gender? - **Context:** Probably becomes painful at Tier 3 (git panel, problem counts) and Tier 4 (pql results). - **Source:** 2026-04-21 planning. diff --git a/decisions/questions-architecture.md b/decisions/questions-architecture.md index dcdd2c22..943bd5bc 100644 --- a/decisions/questions-architecture.md +++ b/decisions/questions-architecture.md @@ -5,105 +5,105 @@ ticket persistence. --- -### Q-001: Authorisation granularity on the IPC socket +### Q-1: Authorisation granularity on the IPC socket - **Status:** Open - **Question:** The daemon's token auth is coarse (allow all / deny all). Do we need per-subsystem grants later (e.g. restrict `git push`), and if so, what's the model — capability tokens? An explicit grant table per client? Time-limited grants? - **Context:** Surfaced in the old ADR 0006 open-questions footer; deferred until Tier 1 is in real use. -- **Source:** ADR 0006 (migrated to [D-006](architecture.md)). +- **Source:** ADR 0006 (migrated to [D-6](architecture.md)). -### Q-002: Back-pressure on event streams +### Q-2: Back-pressure on event streams - **Status:** Open - **Question:** A subscriber that falls behind on `pane.output` (a firehose) needs a policy: drop oldest, block producer, coalesce, or kill subscriber. Which? - **Context:** The event bus is in-memory; back-pressure policy is undefined. Defer until Tier 1 is in real use and we have a real firehose to measure against. -- **Source:** ADR 0006 (migrated to [D-006](architecture.md)). +- **Source:** ADR 0006 (migrated to [D-6](architecture.md)). -### Q-003: Event persistence + audit/undo +### Q-3: Event persistence + audit/undo - **Status:** Open - **Question:** Events are in-memory only in v1. If a future need (audit log, undo history) wants persistence, is it a property of the bus or a subsystem that subscribes and writes? - **Context:** ADR 0006 leaned "subsystem that subscribes and writes" but didn't commit. -- **Source:** ADR 0006 (migrated to [D-006](architecture.md)). +- **Source:** ADR 0006 (migrated to [D-6](architecture.md)). -### Q-004: `.canvas` schema compatibility with Obsidian +### Q-4: `.canvas` schema compatibility with Obsidian - **Status:** Open - **Question:** Clide's canvas (Tier 5) should read/write something — either Obsidian's `.canvas` JSON schema verbatim, a compatible-ish superset, or our own format. Each has trade-offs. - **Context:** Obsidian's canvas users might want their canvases portable; conversely, bending to Obsidian's schema constrains our canvas features. - **Source:** CLAUDE.md "Open questions" footer. -### Q-005: IPC wire-format stability + `schema_version:` +### Q-5: IPC wire-format stability + `schema_version:` - **Status:** Open - **Question:** When do we freeze the IPC envelope / schema and introduce `schema_version:` in `pubspec.yaml`? What's the bump policy for breaking changes? -- **Context:** Covered partially by [D-006](architecture.md)'s `v: 1` starting point; CLAUDE.md flags this as "decide when the first real subcommand lands." +- **Context:** Covered partially by [D-6](architecture.md)'s `v: 1` starting point; CLAUDE.md flags this as "decide when the first real subcommand lands." - **Source:** CLAUDE.md "Open questions" footer. -### Q-006: Window chrome — native frame vs frameless custom -- **Status:** Resolved → [D-057](architecture.md#d-057-frameless-custom-chrome-with-per-column-24px-hats) +### Q-6: Window chrome — native frame vs frameless custom +- **Status:** Resolved → [D-57](architecture.md#d-57-frameless-custom-chrome-with-per-column-24px-hats) - **Question:** Does clide ship with the OS-native window frame (title bar, min/max/close from the WM) or a frameless custom chrome that gives us pixel control at the cost of reimplementing window controls per-platform? - **Context:** Surfaced during Tier-0 plumbing discussion; resolved 2026-04-23 — frameless with per-column 24px hats. - **Source:** 2026-04-21 planning. -### Q-007: macOS app bundle signing / notarisation +### Q-7: macOS app bundle signing / notarisation - **Status:** Open - **Question:** Distributing a signed macOS `.app` requires a Developer ID and a notarisation pipeline. Do we gate macOS builds on this (Tier 6), or ship unsigned with a known "right-click, open" user workflow for early testers? - **Context:** Linux is primary; macOS is a stretch target. Notarisation is a separate cost from the Flutter build. - **Source:** 2026-04-21 planning. -### Q-021: Pql absorbs planning vs keeps separate +### Q-21: Pql absorbs planning vs keeps separate - **Status:** Open -- **Question:** Three shapes for planning tooling's long-term home: (A) Pql absorbs planning — `pql decisions …` + `pql ticket …` subcommands; clide shells out. (B) Clide absorbs pql — reverse [D-003](architecture.md), one big Dart tool. (C) Separate new binary just for planning. -- **Context:** User is leaning (A). This plan assumes (A) without committing. If (A) doesn't land, [D-040](process.md#d-040-python-stopgap-under-toolsscriptsplan)'s sunset condition changes. Gates all tooling work. Integration constraints that shape this question are captured in [D-039](process.md#d-039-planning-tooling-lives-in-pql) / [R-009](rejected.md#r-009-port-planning-tooling-into-clide). +- **Question:** Three shapes for planning tooling's long-term home: (A) Pql absorbs planning — `pql decisions …` + `pql ticket …` subcommands; clide shells out. (B) Clide absorbs pql — reverse [D-3](architecture.md), one big Dart tool. (C) Separate new binary just for planning. +- **Context:** User is leaning (A). This plan assumes (A) without committing. If (A) doesn't land, [D-40](process.md#d-40-python-stopgap-under-toolsscriptsplan)'s sunset condition changes. Gates all tooling work. Integration constraints that shape this question are captured in [D-39](process.md#d-39-planning-tooling-lives-in-pql) / [R-9](rejected.md#r-9-port-planning-tooling-into-clide). - **Source:** 2026-04-21 planning. -### Q-023: SSH-remote development — run clide against a remote workspace +### Q-23: SSH-remote development — run clide against a remote workspace - **Status:** Open - **Question:** Clide today assumes the workspace, the daemon, and the Flutter UI all run on the same machine. A growing class of users edits on remote systems (build servers, GPU boxes, cloud dev environments). What's the architecture for "open repo on host-B from UI on host-A"? Two shapes: (A) daemon-on-remote — clide's Dart daemon runs on the remote; the app talks to it over an SSH-tunnelled unix socket or a dedicated TCP socket (mTLS?), pty/process/filesystem work stays server-side; local app is pure UI. (B) filesystem-mounted — remote mounted via sshfs/9p/rclone, daemon runs locally against the mount; simpler but every fs op + git call crosses the network, and PTYs get complicated (local shell on remote filesystem? ssh-exec per command?). (A) matches VS Code Remote / JetBrains Gateway; (B) matches nothing load-bearing. Sub-questions either way: auth (ssh-agent? per-project keys? OIDC?), tmux / Claude session persistence semantics (does primary-per-repo re-key on host + repo?), multi-host identity in `.pql/pql.db`, latency tolerance for the event stream, re-sync on disconnect. -- **Context:** Surfaced 2026-04-22 during Tier-1 planning. Not a Tier 1 concern — terminal + Claude panes land local-first — but the daemon/IPC seam decisions (notably `D-005` and `D-006`) constrain the future answer. Worth scoping before Tier 6 (extension API) so third-party extensions don't accrue assumptions the remote path would have to unwind. +- **Context:** Surfaced 2026-04-22 during Tier-1 planning. Not a Tier 1 concern — terminal + Claude panes land local-first — but the daemon/IPC seam decisions (notably `D-5` and `D-6`) constrain the future answer. Worth scoping before Tier 6 (extension API) so third-party extensions don't accrue assumptions the remote path would have to unwind. - **Source:** 2026-04-22 planning (user-raised). -### Q-022: Ticket persistence strategy +### Q-22: Ticket persistence strategy - **Status:** Open -- **Question:** Once [Q-021](#q-021-pql-absorbs-planning-vs-keeps-separate) resolves in favour of (A), how do tickets handle shared team state? (1) Never commit (per-dev, ephemeral — works for solo). (2) Commit on milestone (settled-reach's sprint-close pattern — kanban has no natural equivalent, `release` or `tier-cut` is the closest). (3) Markdown mirror — every mutation writes `tickets/T-NNN.md` alongside SQLite; git-legible authoritative record; DB is rebuildable. (3) is probably the eventual answer. +- **Question:** Once [Q-21](#q-21-pql-absorbs-planning-vs-keeps-separate) resolves in favour of (A), how do tickets handle shared team state? (1) Never commit (per-dev, ephemeral — works for solo). (2) Commit on milestone (settled-reach's sprint-close pattern — kanban has no natural equivalent, `release` or `tier-cut` is the closest). (3) Markdown mirror — every mutation writes `tickets/T-NNN.md` alongside SQLite; git-legible authoritative record; DB is rebuildable. (3) is probably the eventual answer. - **Context:** Kanban's lack of a sync event breaks settled-reach's SQLite-authoritative approach the moment two devs collaborate. - **Source:** 2026-04-21 planning. -### Q-025: Body text face — mono everywhere vs Josefin Sans UI + mono code +### Q-25: Body text face — mono everywhere vs Josefin Sans UI + mono code - **Status:** Open - **Question:** The design handoff uses JetBrains Mono for all UI text (tab labels, file paths, status bar, sidebar labels), reserving Josefin Sans only for display/title text. Our current implementation uses Josefin Sans as the ambient UI face with JetBrains Mono only for code/terminal/diff surfaces. Which direction? - **Context:** The design's "mono everywhere" rationale: clide is an IDE for people who like grids. The current Josefin Sans rationale: visual distinction between chrome text and code text, warmer feel. Both are valid — this is a feel decision, not a technical one. - **Source:** 2026-04-22 design handoff review. -### Q-026: Small screen layout (< 1000px) +### Q-26: Small screen layout (< 1000px) - **Status:** Open - **Question:** Below 1000px window width, should clide switch to modal viewer/editor instead of split, or stack panels vertically? The spec defers this but flags it. - **Context:** The interaction model defines breakpoints down to 1200px but punts on < 1000px. This matters for small laptops and tiling WM users who give clide half a screen. - **Source:** 2026-04-22 interaction model spec (Wireframe — Flows v3), open question 1. -### Q-027: Two-editor split +### Q-27: Two-editor split - **Status:** Open - **Question:** Should clide support two files open in the editor simultaneously (horizontal split in the middle column)? The spec says resist until proven needed — feels like tabs creeping back. -- **Context:** [D-048](architecture.md#d-048-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first) deletes buffer tabs. A two-editor split would be the only way to compare files side-by-side without using the viewer ↔ editor swap. The diff view may cover most of this need. +- **Context:** [D-48](architecture.md#d-48-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first) deletes buffer tabs. A two-editor split would be the only way to compare files side-by-side without using the viewer ↔ editor swap. The diff view may cover most of this need. - **Source:** 2026-04-22 interaction model spec (Wireframe — Flows v3), open question 2. -### Q-028: Terminal strip scope — shell only or logs/errors/tests +### Q-28: Terminal strip scope — shell only or logs/errors/tests - **Status:** Open - **Question:** Is the app strip (bottom bar) purely a terminal shell + status, or does it also host tabs for logs, errors, and test output? Probably both, later. - **Context:** The interaction model spec defines the app strip as 14px with terminal shell + daemon indicator + branch; expanding on focus. If it grows to host logs/errors/tests, it becomes a mini-panel with its own tab model. - **Source:** 2026-04-22 interaction model spec (Wireframe — Flows v3), open question 3. -### Q-029: Branch picker location +### Q-29: Branch picker location - **Status:** Open - **Question:** The branch picker was moved out of the bottom status bar. Best place is inside the git section header (left panel), with a compact indicator in the app strip. Confirm or revise? - **Context:** The interaction model spec suggests the git section header as the primary location. The app strip shows a compact indicator (branch name only) for at-a-glance awareness. - **Source:** 2026-04-22 interaction model spec (Wireframe — Flows v3), open question 4. -### Q-030: Focus behavior when editor is dirty and viewer is peeked +### Q-30: Focus behavior when editor is dirty and viewer is peeked - **Status:** Open - **Question:** When the editor has unsaved changes and the user peeks a viewer, where does focus land? The spec says prompt-bar-rule wins: focus stays in Claude. -- **Context:** This intersects [D-047](architecture.md#d-047-interaction-model-claude-is-home-layout) (Claude is home) and [D-049](architecture.md#d-049-editor-mode-inline-above-claude-viewer-swap) (editor mode). If focus always snaps to Claude, the user must explicitly re-focus the editor to continue typing. +- **Context:** This intersects [D-47](architecture.md#d-47-interaction-model-claude-is-home-layout) (Claude is home) and [D-49](architecture.md#d-49-editor-mode-inline-above-claude-viewer-swap) (editor mode). If focus always snaps to Claude, the user must explicitly re-focus the editor to continue typing. - **Source:** 2026-04-22 interaction model spec (Wireframe — Flows v3), open question 5. -### Q-031: XWayland fallback for frameless — proper Wayland protocol needed +### Q-31: XWayland fallback for frameless — proper Wayland protocol needed - **Status:** Open (load-bearing workaround in place) -- **Question:** The frameless window (D-057) currently forces `GDK_BACKEND=x11` because GTK3 doesn't implement the `xdg-decoration` Wayland protocol and KWin ignores `gtk_window_set_decorated(FALSE)` on native Wayland. When do we replace this with a proper implementation? +- **Question:** The frameless window (D-57) currently forces `GDK_BACKEND=x11` because GTK3 doesn't implement the `xdg-decoration` Wayland protocol and KWin ignores `gtk_window_set_decorated(FALSE)` on native Wayland. When do we replace this with a proper implementation? - **Context:** The XWayland fallback works but has tradeoffs: one extra buffer copy per frame, degraded fractional-scaling on HiDPI (125%/150% gets blurry), loss of native Wayland touchpad gestures and per-window DPI, and slightly less reliable cross-app drag-and-drop. For an IDE these are tolerable but not ideal. **Current workaround (`Makefile`):** @@ -123,6 +123,6 @@ ticket persistence. **Alternative timeline:** Flutter moves to GTK4 (which has built-in xdg-decoration support). Track Flutter issue #94381. When it ships, drop all native decoration code and use `gtk_window_set_decorated(FALSE)` — it will just work. -- **Source:** 2026-04-23 D-057 implementation. +- **Source:** 2026-04-23 D-57 implementation. --- diff --git a/decisions/questions-extensions.md b/decisions/questions-extensions.md index 86f47a57..e7f1a7b6 100644 --- a/decisions/questions-extensions.md +++ b/decisions/questions-extensions.md @@ -4,21 +4,21 @@ Extension API shape, Lua runtime vendoring, manifest schema version. --- -### Q-008: Extension API shape — widgets, subcommands, both? +### Q-8: Extension API shape — widgets, subcommands, both? - **Status:** Open -- **Question:** Should extensions contribute widgets (panels, tabs, status-bar items), subcommands (CLI verbs), or both? Both is the obvious answer but has a cost in API surface that must be designed carefully to satisfy user/Claude parity ([D-006](architecture.md)). +- **Question:** Should extensions contribute widgets (panels, tabs, status-bar items), subcommands (CLI verbs), or both? Both is the obvious answer but has a cost in API surface that must be designed carefully to satisfy user/Claude parity ([D-6](architecture.md)). - **Context:** CLAUDE.md flags this as "decide during Tier 6." `builtin.*` stubs today contribute widgets + commands + keybindings; the third-party Lua contract has to match. - **Source:** CLAUDE.md "Open questions" footer. -### Q-009: Lua runtime vendoring +### Q-9: Lua runtime vendoring - **Status:** Open -- **Question:** Does the Lua supporter tool ([D-019](extensions.md#d-019-lua-runtime-as-ptyc-peer-supporter-tool)) bundle liblua source (build with the binary) or link system liblua (smaller binary, fragile ABI)? +- **Question:** Does the Lua supporter tool ([D-19](extensions.md#d-19-lua-runtime-as-ptyc-peer-supporter-tool)) bundle liblua source (build with the binary) or link system liblua (smaller binary, fragile ABI)? - **Context:** `ptyc` has no deps; Lua is different — it's a whole VM. Bundling is the straightforward choice but locks a Lua version per clide release. - **Source:** 2026-04-21 planning. -### Q-010: Extension manifest `schema_version:` +### Q-10: Extension manifest `schema_version:` - **Status:** Open -- **Question:** What's the manifest schema-version scheme and bump policy? Coupled with [Q-005](questions-architecture.md#q-005-ipc-wire-format-stability) (IPC wire format) — both want a versioning story. +- **Question:** What's the manifest schema-version scheme and bump policy? Coupled with [Q-5](questions-architecture.md#q-5-ipc-wire-format-stability) (IPC wire format) — both want a versioning story. - **Context:** Today's manifests have no `schema_version:`. Adding one is cheap; the hard part is deciding when we bump. - **Source:** 2026-04-21 planning. diff --git a/decisions/questions-process.md b/decisions/questions-process.md index 86665a80..f8737c22 100644 --- a/decisions/questions-process.md +++ b/decisions/questions-process.md @@ -7,37 +7,37 @@ Tooling-domain questions currently live here too. Split into --- -### Q-015: Editor tab — full LSP vs tree-sitter-only highlight +### Q-15: Editor tab — full LSP vs tree-sitter-only highlight - **Status:** Open - **Question:** Tier 2's editor tab: do we integrate a full LSP story (analyzer server + hovers + completions + diagnostics) or ship tree-sitter-only syntax highlighting and defer LSP to Tier 6? - **Context:** Full LSP is a large subsystem; tree-sitter is a weekend. User is a heavy LSP user in other IDEs — missing it hurts. CLAUDE.md flags this as "decide during Tier 2." - **Source:** CLAUDE.md "Open questions" footer. -### Q-016: `tree-sitter-dart` grammar maintenance +### Q-16: `tree-sitter-dart` grammar maintenance - **Status:** Open - **Question:** `UserNobody14/tree-sitter-dart` is archived. `nielsenko/tree-sitter-dart` is the maintained fork. Do we pin `nielsenko/`, mirror it in-repo, or lean on the Dart analyzer's own semantic output and skip tree-sitter for Dart? -- **Context:** If tree-sitter is the Tier-2 answer ([Q-015](#q-015-editor-tab-full-lsp-vs-tree-sitter-only)), grammar sourcing matters. +- **Context:** If tree-sitter is the Tier-2 answer ([Q-15](#q-15-editor-tab-full-lsp-vs-tree-sitter-only)), grammar sourcing matters. - **Source:** 2026-04-21 planning. -### Q-017: Icon set growth +### Q-17: Icon set growth - **Status:** Open - **Question:** Hand-drawn `CustomPainter` catalogue (total control, pixel-perfect on every theme, slow to grow) vs SVG + parser (faster to grow, one more dep, theming is harder)? -- **Context:** We rejected Nerd-font glyphs ([R-006](rejected.md#r-006-nerd-font-glyph-icons)); something has to fill the gap. +- **Context:** We rejected Nerd-font glyphs ([R-6](rejected.md#r-6-nerd-font-glyph-icons)); something has to fill the gap. - **Source:** 2026-04-21 planning. -### Q-018: Theme hot-reload in release builds +### Q-18: Theme hot-reload in release builds - **Status:** Open - **Question:** The theme picker supports live-reloading a YAML during development. Does the same path stay open in release builds (user tweaks `~/.config/clide/themes/foo.yaml` and the app re-reads on focus) or is release-build theming restricted to built-in + settings-UI-installed themes? - **Context:** Hot-reload is powerful for theme authoring but opens a file-watch + re-parse path in release code. - **Source:** 2026-04-21 planning. -### Q-019: (withdrawn) +### Q-19: (withdrawn) - **Status:** Resolved → n/a -- **Note:** Earlier floated as "ticket markdown mirror vs SQLite" — no longer a split question. Markdown mirror is tracked in [Q-022](questions-architecture.md#q-022-ticket-persistence-strategy); SQLite is the current stopgap per [D-040](process.md#d-040-python-stopgap-under-toolsscriptsplan). +- **Note:** Earlier floated as "ticket markdown mirror vs SQLite" — no longer a split question. Markdown mirror is tracked in [Q-22](questions-architecture.md#q-22-ticket-persistence-strategy); SQLite is the current stopgap per [D-40](process.md#d-40-python-stopgap-under-toolsscriptsplan). -### Q-020: Kernel DB service — namespaced SQL access? +### Q-20: Kernel DB service — namespaced SQL access? - **Status:** Open -- **Question:** Do extensions get namespaced SQL access to `.clide/clide.db` (tables prefixed `ext__…`) or stay on the `kernel.settings` key/value facade? Admission-level question ([D-012](architecture.md#d-012-kernel-admission-rule)). +- **Question:** Do extensions get namespaced SQL access to `.clide/clide.db` (tables prefixed `ext__…`) or stay on the `kernel.settings` key/value facade? Admission-level question ([D-12](architecture.md#d-12-kernel-admission-rule)). - **Context:** Some extensions (tickets, canvas, graph) naturally want relational storage. K/V gets awkward fast. - **Source:** 2026-04-21 planning. diff --git a/decisions/questions-testing.md b/decisions/questions-testing.md index f664d326..ababf77b 100644 --- a/decisions/questions-testing.md +++ b/decisions/questions-testing.md @@ -2,15 +2,15 @@ --- -### Q-011: Coverage gates — hard thresholds vs soft reporting +### Q-11: Coverage gates — hard thresholds vs soft reporting - **Status:** Open - **Question:** `ci/test_coverage.sh` emits lcov + a summary. Do we gate merges on a hard threshold (fail < 80%), report softly, or tier per directory (kernel > 90%, built-ins > 70%, widgets covered by goldens exempt)? - **Context:** Hard thresholds force tests-for-coverage-sake; soft reporting gets ignored. - **Source:** 2026-04-21 planning. -### Q-012: Screen-reader automation (axe-core via Playwright) +### Q-12: Screen-reader automation (axe-core via Playwright) - **Status:** Open -- **Question:** [D-022](accessibility.md#d-022-wcag-aa-contrast-gate-on-bundled-themes) gates contrast at build time. Do we also run axe-core against the WASM build in Playwright for runtime a11y issues (missing labels, invalid roles, orphan focusables)? +- **Question:** [D-22](accessibility.md#d-22-wcag-aa-contrast-gate-on-bundled-themes) gates contrast at build time. Do we also run axe-core against the WASM build in Playwright for runtime a11y issues (missing labels, invalid roles, orphan focusables)? - **Context:** axe-core is JS; runs in the browser against the rendered tree. Extra CI time; extra signal. - **Source:** 2026-04-21 planning. diff --git a/decisions/questions.md b/decisions/questions.md index 86880088..f6562612 100644 --- a/decisions/questions.md +++ b/decisions/questions.md @@ -16,9 +16,9 @@ open questions with one-line summaries. ## Load-bearing questions (gate other work) -- **[Q-021](questions-process.md#q-021-pql-absorbs-planning-vs-keeps-separate)** — Pql absorbs planning features vs clide absorbs pql vs separate CLI. Blocks the stopgap sunset and shapes the pql-side planning session. -- **[Q-022](questions-process.md#q-022-ticket-persistence-strategy)** — Ticket persistence: per-dev only / milestone-committed / markdown-mirrored. Shapes multi-contributor story. -- **[Q-005](questions-architecture.md#q-005-ipc-wire-format-stability)** — IPC wire-format stability and `schema_version:` in `pubspec.yaml`. Decide when the first real subcommand lands. -- **[Q-015](questions-process.md#q-015-editor-tab-full-lsp-vs-tree-sitter-only)** — Editor tab: full LSP integration vs tree-sitter-only highlight. Decide during Tier 2. +- **[Q-21](questions-process.md#q-21-pql-absorbs-planning-vs-keeps-separate)** — Pql absorbs planning features vs clide absorbs pql vs separate CLI. Blocks the stopgap sunset and shapes the pql-side planning session. +- **[Q-22](questions-process.md#q-22-ticket-persistence-strategy)** — Ticket persistence: per-dev only / milestone-committed / markdown-mirrored. Shapes multi-contributor story. +- **[Q-5](questions-architecture.md#q-5-ipc-wire-format-stability)** — IPC wire-format stability and `schema_version:` in `pubspec.yaml`. Decide when the first real subcommand lands. +- **[Q-15](questions-process.md#q-15-editor-tab-full-lsp-vs-tree-sitter-only)** — Editor tab: full LSP integration vs tree-sitter-only highlight. Decide during Tier 2. --- diff --git a/decisions/rejected.md b/decisions/rejected.md index 11357d0b..7e6bdf52 100644 --- a/decisions/rejected.md +++ b/decisions/rejected.md @@ -5,59 +5,59 @@ future reference. --- -### R-002: Go sidecar -- **Rejected:** 2026-04-20 (was ADR 0002; superseded by [D-005](architecture.md#d-005-dart-core-ptyc-peer)) +### R-2: Go sidecar +- **Rejected:** 2026-04-20 (was ADR 0002; superseded by [D-5](architecture.md#d-5-dart-core-ptyc-peer)) - **Reason:** The ADR picked Go on two premises — (a) the heavy work belongs in a language separate from the UI layer, and (b) pql is Go so muscle memory transfers. Both broke on reassessment. The sidecar stripped of PTY is I/O-bound glue that `dart:io` covers cleanly (unix sockets, JSON-lines framing, process tables, shell-outs). The real axis was *separate process vs shared language*, not Go vs Rust, and separate-process is what matters (session persistence needs the daemon to outlive the app), not language. PTY is the one place Dart is genuinely weak — Dart's multi-threaded VM can't safely `fork()` — and that single constraint forces a native helper regardless, independent of whether the rest of the core is Dart. Once a small native helper is accepted, the question "does *everything else* need to be in that same native language" answers itself: no. Go sidecar directory dissolved; `ptyc` (C, PTY-only, pql-peer) is the surviving native supporter tool. -- **Cross-reference:** [D-005](architecture.md#d-005-dart-core-ptyc-peer) +- **Cross-reference:** [D-5](architecture.md#d-5-dart-core-ptyc-peer) -### R-003: `MaterialApp` root +### R-3: `MaterialApp` root - **Rejected:** 2026-04-21 -- **Reason:** Dragged in Material theming, default icons, and platform chrome that fought the custom three-tier theme pipeline ([D-009](architecture.md#d-009-three-tier-theme-pipeline)). Every bundled theme had to override Material defaults to look like clide; the overrides were visible in widget tests as "why is this `ElevatedButton` colored this way." -- **Cross-reference:** [D-007](architecture.md#d-007-app-root-is-bare-widgetsapp) +- **Reason:** Dragged in Material theming, default icons, and platform chrome that fought the custom three-tier theme pipeline ([D-9](architecture.md#d-9-three-tier-theme-pipeline)). Every bundled theme had to override Material defaults to look like clide; the overrides were visible in widget tests as "why is this `ElevatedButton` colored this way." +- **Cross-reference:** [D-7](architecture.md#d-7-app-root-is-bare-widgetsapp) -### R-004: Flutter `intl` + ARB codegen for i18n +### R-4: Flutter `intl` + ARB codegen for i18n - **Rejected:** 2026-04-21 - **Reason:** ARB codegen is inflexible for plugin-contributed catalogs — every catalogue needs a codegen pass, every extension ships with pre-generated Dart, and runtime merging is fighting the tool. The fframe text-driven pattern reads JSON at runtime with no codegen, which fits extension-shipped catalogs cleanly. -- **Cross-reference:** [D-021](accessibility.md#d-021-i18n-is-a-tier-0-contract) +- **Cross-reference:** [D-21](accessibility.md#d-21-i18n-is-a-tier-0-contract) -### R-005: Patrol test runner +### R-5: Patrol test runner - **Rejected:** 2026-04-21 -- **Reason:** Adds a dependency (violates [D-031](tooling.md#d-031-prefer-zero-deps-exact-pin)) for a capability we get from Playwright + Flutter's own semantics tree. Patrol's value proposition (native-gesture emulation) is less relevant on Linux desktop than on mobile. -- **Cross-reference:** [D-026](testing.md#d-026-web-driver-raw-playwright-plus-flutter-semantics) +- **Reason:** Adds a dependency (violates [D-31](tooling.md#d-31-prefer-zero-deps-exact-pin)) for a capability we get from Playwright + Flutter's own semantics tree. Patrol's value proposition (native-gesture emulation) is less relevant on Linux desktop than on mobile. +- **Cross-reference:** [D-26](testing.md#d-26-web-driver-raw-playwright-plus-flutter-semantics) -### R-006: Nerd-font glyph icons +### R-6: Nerd-font glyph icons - **Rejected:** 2026-04-21 - **Reason:** TUI hangover from the Python-era clide under `legacy/`. Not desktop-native; forces a font dependency; doesn't theme consistently. Clide uses custom icon primitives (Tier 6 revisits with proper icon-set design). -- **Cross-reference:** [Q-017](questions-process.md#q-017-icon-set-growth) +- **Cross-reference:** [Q-17](questions-process.md#q-17-icon-set-growth) -### R-007: `CupertinoApp` root +### R-7: `CupertinoApp` root - **Rejected:** 2026-04-21 -- **Reason:** iOS-opinionated; wrong shell for a Linux-primary desktop IDE. Same theming-collision problem as [R-003](#r-003-materialapp-root). -- **Cross-reference:** [D-007](architecture.md#d-007-app-root-is-bare-widgetsapp) +- **Reason:** iOS-opinionated; wrong shell for a Linux-primary desktop IDE. Same theming-collision problem as [R-3](#r-3-materialapp-root). +- **Cross-reference:** [D-7](architecture.md#d-7-app-root-is-bare-widgetsapp) -### R-008: Riverpod / Provider / BLoC for state +### R-8: Riverpod / Provider / BLoC for state - **Rejected:** 2026-04-21 -- **Reason:** Violates [D-031](tooling.md#d-031-prefer-zero-deps-exact-pin). `ChangeNotifier` + `ListenableBuilder` ship in the SDK, fake trivially, and cover the state model we need. The ergonomic wins of Riverpod / Provider don't clear the "new dependency" bar at clide's scale. -- **Cross-reference:** [D-010](architecture.md#d-010-state-management-changenotifier) +- **Reason:** Violates [D-31](tooling.md#d-31-prefer-zero-deps-exact-pin). `ChangeNotifier` + `ListenableBuilder` ship in the SDK, fake trivially, and cover the state model we need. The ergonomic wins of Riverpod / Provider don't clear the "new dependency" bar at clide's scale. +- **Cross-reference:** [D-10](architecture.md#d-10-state-management-changenotifier) -### R-009: Port planning tooling into clide +### R-9: Port planning tooling into clide - **Rejected:** 2026-04-21 -- **Reason:** Earlier in the planning session the assumption was "clide owns Dart subcommands for decisions + tickets." That breaks the day a contributor works in a terminal or in VS Code / JetBrains — they have no `clide` binary to run. Reversing: pql owns planning long-term (see [D-039](process.md#d-039-planning-tooling-lives-in-pql)); clide consumes via shell-out. -- **Cross-reference:** [D-039](process.md#d-039-planning-tooling-lives-in-pql) +- **Reason:** Earlier in the planning session the assumption was "clide owns Dart subcommands for decisions + tickets." That breaks the day a contributor works in a terminal or in VS Code / JetBrains — they have no `clide` binary to run. Reversing: pql owns planning long-term (see [D-39](process.md#d-39-planning-tooling-lives-in-pql)); clide consumes via shell-out. +- **Cross-reference:** [D-39](process.md#d-39-planning-tooling-lives-in-pql) -### R-010: Python-script stopgap under `tooling/db/` +### R-10: Python-script stopgap under `tooling/db/` - **Rejected:** 2026-04-21 -- **Reason:** Location, not language. Settled-reach puts scripts at `tooling/db/` — copying that path here creates a script-pollution problem: every project using the pattern commits its own copy. The accepted Python port ([D-040](process.md#d-040-python-stopgap-under-toolsscriptsplan)) lives at `tools/scripts/plan`, clearly signalled as dev-tooling and time-limited. -- **Cross-reference:** [D-040](process.md#d-040-python-stopgap-under-toolsscriptsplan) +- **Reason:** Location, not language. Settled-reach puts scripts at `tooling/db/` — copying that path here creates a script-pollution problem: every project using the pattern commits its own copy. The accepted Python port ([D-40](process.md#d-40-python-stopgap-under-toolsscriptsplan)) lives at `tools/scripts/plan`, clearly signalled as dev-tooling and time-limited. +- **Cross-reference:** [D-40](process.md#d-40-python-stopgap-under-toolsscriptsplan) -### R-011: Permanent stopgap +### R-11: Permanent stopgap - **Rejected:** 2026-04-21 - **Reason:** If the Python port under `tools/scripts/plan` outlasts pql's feature parity, delete it. The deletion commit should be one changeset: remove `tools/scripts/plan`, remove its Makefile target (`decisions-validate` rewires to `pql decisions validate`), add a `CHANGELOG.md` entry under Removed, and verify `.pql/pql.db` still opens under the new `pql` binary. -- **Cross-reference:** [D-040](process.md#d-040-python-stopgap-under-toolsscriptsplan) +- **Cross-reference:** [D-40](process.md#d-40-python-stopgap-under-toolsscriptsplan) -### R-012: MaterialApp wrapper from design handoff +### R-12: MaterialApp wrapper from design handoff - **Rejected:** 2026-04-22 -- **Reason:** The design handoff delivers theme files as `MaterialApp`/`ThemeData` Dart classes. This is the delivery format of claude.ai/design, not a design intent. Adopting Material's widget system would contradict [D-007](architecture.md#d-007-app-root-is-bare-widgetsapp) (bare WidgetsApp, no Material/Cupertino). We translate the palette tokens and syntax roles into our existing YAML + `SurfaceTokens` pipeline. -- **Cross-reference:** [D-043](architecture.md#d-043-design-handoff-adopt-token-palettes-reject-material-wrapper) +- **Reason:** The design handoff delivers theme files as `MaterialApp`/`ThemeData` Dart classes. This is the delivery format of claude.ai/design, not a design intent. Adopting Material's widget system would contradict [D-7](architecture.md#d-7-app-root-is-bare-widgetsapp) (bare WidgetsApp, no Material/Cupertino). We translate the palette tokens and syntax roles into our existing YAML + `SurfaceTokens` pipeline. +- **Cross-reference:** [D-43](architecture.md#d-43-design-handoff-adopt-token-palettes-reject-material-wrapper) --- diff --git a/decisions/testing.md b/decisions/testing.md index 5a868ffd..bb05c698 100644 --- a/decisions/testing.md +++ b/decisions/testing.md @@ -4,56 +4,56 @@ Test pyramid, drivers, client-side constraint. --- -### D-023: Test pyramid — seven layers +### D-23: Test pyramid — seven layers - **Date:** 2026-04-21 - **Decision:** The pyramid has seven layers: unit (pure Dart) → widget (pumped + find) → golden (visual primitives) → a11y (semantics coverage + keyboard + contrast + i18n) → integration (`flutter test integration_test/`) → E2E (Playwright driving the WASM build + `clide --daemon` subprocess) → startup-smoke (`ci/smoke_bundle.sh`: build Linux release, run under xvfb for 5 s). - **Rationale:** Each layer catches a distinct regression class. Skipping any layer means that class ships unprotected. Pushed back when earlier rounds proposed "just widget + E2E"; widget can't catch paint regressions (that's golden), E2E can't catch a11y tree drift (that's semantics). -- **Cost:** Seven CI jobs; total wall time budgeted at < 15 min. Pre-push runs layers 1-4 (< 90 s — see [D-029](#d-029-pre-push-gate-fast-layer-only)). +- **Cost:** Seven CI jobs; total wall time budgeted at < 15 min. Pre-push runs layers 1-4 (< 90 s — see [D-29](#d-29-pre-push-gate-fast-layer-only)). - **Raised by:** 2026-04-21 planning. -### D-024: Golden tests — primitives only, Alchemist + Ahem +### D-24: Golden tests — primitives only, Alchemist + Ahem - **Date:** 2026-04-21 - **Decision:** Goldens cover primitive widgets only (button, tab, panel header, token-bound surfaces). Composed layouts are tested via widget-find assertions, not pixel goldens. Golden rendering uses Alchemist with the Ahem font to get deterministic text metrics across platforms. - **Rationale:** Pixel goldens of composed layouts churn constantly (one tweak → fifty golden diffs) without catching more than primitive goldens would. Alchemist + Ahem sidesteps the "font rendering differs between Linux CI and macOS dev" trap. - **Cost:** Goldens have zero real text; layouts rely on widget tests. Acceptable. - **Raised by:** 2026-04-21 planning. -### D-025: Mocks — mocktail at IO, hand-rolled fakes for ChangeNotifiers +### D-25: Mocks — mocktail at IO, hand-rolled fakes for ChangeNotifiers - **Date:** 2026-04-21 - **Decision:** `mocktail 1.0.4` mocks IO boundaries (sockets, processes, `dart:io` File/Directory). `ChangeNotifier` facades get hand-rolled fakes — tiny classes that extend `ChangeNotifier` with test-controlled setters. No `mocktail` for notifiers. - **Rationale:** Mocking a `ChangeNotifier` with a generated mock hides subscription bugs — `notifyListeners` becomes a mock call instead of actually firing. Hand-rolled fakes exercise the real subscription machinery. - **Cost:** Roughly 20 lines per fake. Rounds out to less code than configuring a mocktail whenCall chain. - **Raised by:** 2026-04-21 planning. -### D-026: Web driver — raw Playwright + Flutter semantics +### D-26: Web driver — raw Playwright + Flutter semantics - **Date:** 2026-04-21 - **Decision:** The browser-side E2E driver uses Playwright directly against Flutter's semantics tree (`flt-semantics[aria-label]`). No Patrol, no flutter_driver for web. The driver (`tools/ui/driver.ts`) clicks `flt-semantics-placeholder` on load to activate semantics, then queries by substring aria-label (Flutter merges sibling labels). -- **Rationale:** Patrol adds a dependency for a capability we get from semantics + Playwright directly. Labels are the a11y tree we already contract to maintain ([D-020](accessibility.md#d-020-a11y-is-a-tier-0-contract)); reusing them for E2E is a win. +- **Rationale:** Patrol adds a dependency for a capability we get from semantics + Playwright directly. Labels are the a11y tree we already contract to maintain ([D-20](accessibility.md#d-20-a11y-is-a-tier-0-contract)); reusing them for E2E is a win. - **Cost:** Driver has to know Flutter's sibling-merging behaviour — documented in `docs/testing/claude-ui-workflow.md`. - **Raised by:** 2026-04-21 planning. -### D-027: Startup regression gate +### D-27: Startup regression gate - **Date:** 2026-04-21 - **Decision:** Two gates guard boot regressions: `integration_test/app_starts_test.dart` (fast — boots the app under `flutter test`) and `ci/smoke_bundle.sh` (slow — `flutter build linux`, run the bundle under `xvfb` for 5 s, assert no exit code). Both run in CI as `startup-bundle` job. - **Rationale:** The fast integration test catches "boot hangs in Dart land"; the bundle smoke catches "boot breaks under release compile + production xvfb" — different regression classes. - **Cost:** One extra CI job + `xvfb` on the runner. Five seconds of boot is enough; we've already caught one regression at this gate. - **Raised by:** 2026-04-21 planning. -### D-028: Test organisation — mirror `lib/` in `test/` +### D-28: Test organisation — mirror `lib/` in `test/` - **Date:** 2026-04-21 - **Decision:** Every test file lives at the same relative path as its subject. `app/lib/kernel/src/i18n/catalog_loader.dart` pairs with `app/test/kernel/i18n/catalog_loader_test.dart`. No separate `unit/` vs `widget/` directories; test type is detected by what the test imports. - **Rationale:** Matching paths makes "jump to test" predictable in any editor. Type-by-imports matches how `flutter test` already works. - **Cost:** Large feature folders mirror into large test folders. Acceptable. - **Raised by:** 2026-04-21 planning. -### D-029: Pre-push gate — fast layer only +### D-29: Pre-push gate — fast layer only - **Date:** 2026-04-21 - **Decision:** `make push-check` runs analyze + format + unit + widget + golden + a11y, target < 90 s. Integration, E2E, and startup-bundle run in CI but not on pre-push. - **Rationale:** Pre-push gates that exceed ~90 s get disabled by muscle memory ("just push, it'll catch in CI"). Keeping the gate fast keeps it respected. Integration + E2E + bundle still gate merge via CI. - **Cost:** Some regressions land on `main` that CI catches. Rollback or hotfix — acceptable for a solo-or-small-team cadence. - **Raised by:** 2026-04-21 planning. -### D-030: Tests are client-side only +### D-30: Tests are client-side only - **Date:** 2026-04-21 - **Decision:** No test hits the network. No test depends on remote fixtures, shared DBs, or state outside the test process. Fakes and fixtures live in-tree. - **Rationale:** Network-dependent tests flake; flaky tests get quarantined; quarantined tests get deleted. Client-side-only makes CI deterministic offline. diff --git a/decisions/tooling.md b/decisions/tooling.md index 80f6e9f6..97b60f03 100644 --- a/decisions/tooling.md +++ b/decisions/tooling.md @@ -4,42 +4,42 @@ Toolchain, supply chain, CI, ignore strategy. --- -### D-031: Prefer-zero-deps, exact-pin +### D-31: Prefer-zero-deps, exact-pin - **Date:** 2026-04-21 - **Decision:** Default to writing code ourselves. Every third-party Dart dependency needs a paragraph of justification in the PR that adds it. What stays is exact-pinned in `pubspec.yaml` (no caret ranges), `pubspec.lock` is committed, and advisories are reviewed before every bump. - **Rationale:** Supply-chain gate. Flutter SDK + Dart SDK give us most of what we need; the dependencies we keep are the ones we can't reasonably write (yaml parser, mocktail, alchemist). Exact-pin because caret ranges mean "the CVE bumps itself in silently." - **Cost:** Longer PR descriptions for deps; occasional reinvention of a convenience. Accepted. - **Raised by:** 2026-04-21 planning; reinforced by user feedback memory. -### D-032: CI — Gitea primary, Linux-only runners, not yet activated +### D-32: CI — Gitea primary, Linux-only runners, not yet activated - **Date:** 2026-04-21 - **Decision:** CI config lives at `.gitea/workflows/test.yml` (Gitea Actions consumes GitHub-Actions syntax). Runners are Linux only; macOS is tested locally. The workflow is ready but Gitea Actions is not yet activated on the instance — the file is a staged pipeline for review. If the repo moves to GitHub, the file copies to `.github/workflows/test.yml` verbatim. - **Rationale:** We want the CI story defined before we turn CI on — lower blast radius on early red builds. GitHub portability is free because the syntax is shared. - **Cost:** PRs don't run CI yet; `make push-check` is the gate until activation. - **Raised by:** 2026-04-21 planning. -### D-042: Dependencies documented in `licenses.yaml` +### D-42: Dependencies documented in `licenses.yaml` - **Date:** 2026-04-22 - **Decision:** `app/assets/licenses.yaml` has three sections: `self:` (clide's MIT license, rendered first in the About screen so the user knows what they're running), `dependencies:` (third-party artefacts that **ship in the binary** — fonts, runtime Dart packages, native supporter tools, bundled data), and `dev_dependencies:` (build-time-only tooling — test runners, mocks, lints, golden harness — tracked for audit but **not rendered** in the About screen because they don't reach the user). Each entry has name, kind, version, homepage, license identifier, and a one-line purpose; runtime entries also carry a `license_file:` pointer to the bundled license text so the About screen can display it verbatim. Adding any dependency is a two-step commit: add the artefact **and** the corresponding `licenses.yaml` entry in the same changeset, under the correct section. -- **Rationale:** Complements [D-031](#d-031-prefer-zero-deps-exact-pin). Prefer-zero-deps is a *budget*; `licenses.yaml` is the *visible consequence*. An extra row in the About screen is a review-time signal that the shipped-binary surface grew. Splitting dev deps out keeps the user-facing list small and honest — a test framework is not something the user needs to see in About — while still documenting every supply-chain input for audit completeness. The runtime entries discharge the redistribution obligations bundled licenses impose (OFL, MIT, BSD all require preserving the license text alongside the binary) without ad-hoc NOTICE files. +- **Rationale:** Complements [D-31](#d-31-prefer-zero-deps-exact-pin). Prefer-zero-deps is a *budget*; `licenses.yaml` is the *visible consequence*. An extra row in the About screen is a review-time signal that the shipped-binary surface grew. Splitting dev deps out keeps the user-facing list small and honest — a test framework is not something the user needs to see in About — while still documenting every supply-chain input for audit completeness. The runtime entries discharge the redistribution obligations bundled licenses impose (OFL, MIT, BSD all require preserving the license text alongside the binary) without ad-hoc NOTICE files. - **Cost:** One extra edit per dep. Zero tolerance for drift — an un-listed dep is a contributor-visible bug. Until the About screen lands at Tier 6, `licenses.yaml` is accurate but not rendered; the discipline applies from now regardless so Tier 6 inherits a clean list. - **Raised by:** 2026-04-22 planning (user-directed best practice). -### D-033: Golden-output ignore pattern — `coverage.*` excludes output, not scripts +### D-33: Golden-output ignore pattern — `coverage.*` excludes output, not scripts - **Date:** 2026-04-21 - **Decision:** `.gitignore` excludes `coverage.*` (the lcov output files from `flutter test --coverage`). Coverage-related scripts are named `ci/test_coverage.sh` (not `ci/coverage.sh`) to stay outside the pattern. - **Rationale:** An earlier draft named the script `ci/coverage.sh` and it was silently git-ignored. Renaming the script is cheaper than narrowing the gitignore pattern (which risks re-introducing output churn). - **Cost:** Script names have a convention to follow. - **Raised by:** 2026-04-21 planning (caught during commit rehearsal). -### D-058: Format engines are adoptable dependencies +### D-58: Format engines are adoptable dependencies - **Date:** 2026-04-23 - **Decision:** The "own the rendering stack" guardrail applies to **UI chrome** — panels, tabs, panes, canvas, terminal, layout primitives. **Format engines** — packages that parse or render external file formats (SVG, markdown, HTML, terminal escape sequences, tree-sitter grammars) — are adoptable like any other dependency: vet, exact-pin, CVE-lock, document in `licenses.yaml`. They are not shortcuts for lazy coding; they are well-maintained renderers for formats we didn't invent. The distinction: if it renders *our* UI, we own it; if it renders *someone else's file format*, we adopt a parser/renderer and sandbox it. - **Adopted under this rule:** `jovial_svg` (SVG renderer), `markdown` (MD parser; renderer is ours), `flutter_widget_from_html_core` (HTML renderer; sandboxed), `xterm` (terminal emulator), tree-sitter (syntax highlighting). Canvas (`CustomPaint` + `InteractiveViewer`) stays in-house — UI chrome, not a format engine. -- **Amendment to D-031 (prefer-zero-deps):** D-031's "prefer-zero-deps" still applies — every new dependency needs justification. This record clarifies that format engines clear the justification bar by default. The supply-chain gate (exact-pin, advisory review, `licenses.yaml`) still applies. +- **Amendment to D-31 (prefer-zero-deps):** D-31's "prefer-zero-deps" still applies — every new dependency needs justification. This record clarifies that format engines clear the justification bar by default. The supply-chain gate (exact-pin, advisory review, `licenses.yaml`) still applies. - **Rationale:** Reimplementing SVG, markdown, or VT100 parsing adds months of work for no fidelity gain. tree-sitter already set this precedent. The key is sandboxing: HTML rendering must whitelist tags/attributes; SVG must not execute scripts; markdown rendering goes through our own widget builder so we control the output. - **Cost:** Each adopted engine adds transitive dependencies and supply-chain surface. Mitigated by exact-pinning and `make security`. -- **Cross-reference:** [D-031](#d-031-prefer-zero-deps-exact-pin), [D-042](#d-042-dependencies-documented-in-licensesyaml). +- **Cross-reference:** [D-31](#d-31-prefer-zero-deps-exact-pin), [D-42](#d-42-dependencies-documented-in-licensesyaml). - **Raised by:** 2026-04-23 format engine evaluation. --- diff --git a/pql-plan.json b/pql-plan.json new file mode 100644 index 00000000..87123e56 --- /dev/null +++ b/pql-plan.json @@ -0,0 +1,2759 @@ +{ + "exported_at": "2026-04-23T10:05:07Z", + "decisions": [ + { + "id": "D-1", + "type": "confirmed", + "domain": "architecture", + "title": "CLI-first, not MCP", + "status": "active", + "date": "2026-04-20", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-1", + "type": "question", + "domain": "architecture", + "title": "Authorisation granularity on the IPC socket", + "status": "open", + "file_path": "decisions/questions-architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-2", + "type": "question", + "domain": "architecture", + "title": "Back-pressure on event streams", + "status": "open", + "file_path": "decisions/questions-architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "R-2", + "type": "rejected", + "domain": "rejected", + "title": "Go sidecar", + "status": "active", + "date": "2026-04-20", + "file_path": "decisions/rejected.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-3", + "type": "confirmed", + "domain": "architecture", + "title": "pql as supporter tool; clide wraps, never duplicates", + "status": "active", + "date": "2026-04-20", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-3", + "type": "question", + "domain": "architecture", + "title": "Event persistence + audit/undo", + "status": "open", + "file_path": "decisions/questions-architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "R-3", + "type": "rejected", + "domain": "rejected", + "title": "`MaterialApp` root", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/rejected.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-4", + "type": "confirmed", + "domain": "architecture", + "title": "Ignore file strategy", + "status": "active", + "date": "2026-04-20", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-4", + "type": "question", + "domain": "architecture", + "title": "`.canvas` schema compatibility with Obsidian", + "status": "open", + "file_path": "decisions/questions-architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "R-4", + "type": "rejected", + "domain": "rejected", + "title": "Flutter `intl` + ARB codegen for i18n", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/rejected.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-5", + "type": "confirmed", + "domain": "architecture", + "title": "Dart core; sidecar dissolved; `ptyc` as pql-peer", + "status": "active", + "date": "2026-04-20", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-5", + "type": "question", + "domain": "architecture", + "title": "IPC wire-format stability + `schema_version:`", + "status": "open", + "file_path": "decisions/questions-architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "R-5", + "type": "rejected", + "domain": "rejected", + "title": "Patrol test runner", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/rejected.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-6", + "type": "confirmed", + "domain": "architecture", + "title": "CLI and event surface contract", + "status": "active", + "date": "2026-04-20", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-6", + "type": "question", + "domain": "architecture", + "title": "Window chrome \u2014 native frame vs frameless custom", + "status": "resolved", + "file_path": "decisions/questions-architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "R-6", + "type": "rejected", + "domain": "rejected", + "title": "Nerd-font glyph icons", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/rejected.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-7", + "type": "confirmed", + "domain": "architecture", + "title": "App root is bare `WidgetsApp`", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-7", + "type": "question", + "domain": "architecture", + "title": "macOS app bundle signing / notarisation", + "status": "open", + "file_path": "decisions/questions-architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "R-7", + "type": "rejected", + "domain": "rejected", + "title": "`CupertinoApp` root", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/rejected.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-8", + "type": "confirmed", + "domain": "architecture", + "title": "Feature-first folder layout", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-8", + "type": "question", + "domain": "extensions", + "title": "Extension API shape \u2014 widgets, subcommands, both?", + "status": "open", + "file_path": "decisions/questions-extensions.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "R-8", + "type": "rejected", + "domain": "rejected", + "title": "Riverpod / Provider / BLoC for state", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/rejected.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-9", + "type": "confirmed", + "domain": "architecture", + "title": "Three-tier theme pipeline", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-9", + "type": "question", + "domain": "extensions", + "title": "Lua runtime vendoring", + "status": "open", + "file_path": "decisions/questions-extensions.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "R-9", + "type": "rejected", + "domain": "rejected", + "title": "Port planning tooling into clide", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/rejected.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-10", + "type": "confirmed", + "domain": "architecture", + "title": "State management \u2014 `ChangeNotifier` + `ListenableBuilder`", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-10", + "type": "question", + "domain": "extensions", + "title": "Extension manifest `schema_version:`", + "status": "open", + "file_path": "decisions/questions-extensions.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "R-10", + "type": "rejected", + "domain": "rejected", + "title": "Python-script stopgap under `tooling/db/`", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/rejected.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-11", + "type": "confirmed", + "domain": "architecture", + "title": "Panel manager is kernel; layout is data; three-column is a preset", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-11", + "type": "question", + "domain": "testing", + "title": "Coverage gates \u2014 hard thresholds vs soft reporting", + "status": "open", + "file_path": "decisions/questions-testing.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "R-11", + "type": "rejected", + "domain": "rejected", + "title": "Permanent stopgap", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/rejected.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-12", + "type": "confirmed", + "domain": "architecture", + "title": "Kernel admission rule \u2014 mandatory shared singletons only", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-12", + "type": "question", + "domain": "testing", + "title": "Screen-reader automation (axe-core via Playwright)", + "status": "open", + "file_path": "decisions/questions-testing.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "R-12", + "type": "rejected", + "domain": "rejected", + "title": "MaterialApp wrapper from design handoff", + "status": "active", + "date": "2026-04-22", + "file_path": "decisions/rejected.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-13", + "type": "confirmed", + "domain": "architecture", + "title": "Git hardcoded in kernel project-loader", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-13", + "type": "question", + "domain": "accessibility", + "title": "Web production-mode a11y", + "status": "open", + "file_path": "decisions/questions-accessibility.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-14", + "type": "confirmed", + "domain": "architecture", + "title": "Two-tier disable \u2014 kernel locked, everything else extension-shaped", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-14", + "type": "question", + "domain": "accessibility", + "title": "i18n plurals / gender / date-format tooling", + "status": "open", + "file_path": "decisions/questions-accessibility.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-15", + "type": "confirmed", + "domain": "extensions", + "title": "Extension grain \u2014 container-level, multi-contribution", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/extensions.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-15", + "type": "question", + "domain": "process", + "title": "Editor tab \u2014 full LSP vs tree-sitter-only highlight", + "status": "open", + "file_path": "decisions/questions-process.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-16", + "type": "confirmed", + "domain": "extensions", + "title": "Built-ins in Dart, third-party in sandboxed Lua", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/extensions.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-16", + "type": "question", + "domain": "process", + "title": "`tree-sitter-dart` grammar maintenance", + "status": "open", + "file_path": "decisions/questions-process.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-17", + "type": "confirmed", + "domain": "extensions", + "title": "Panels are extension-shaped from day one", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/extensions.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-17", + "type": "question", + "domain": "process", + "title": "Icon set growth", + "status": "open", + "file_path": "decisions/questions-process.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-18", + "type": "confirmed", + "domain": "extensions", + "title": "YAML for themes + manifests; JSON for i18n catalogs", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/extensions.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-18", + "type": "question", + "domain": "process", + "title": "Theme hot-reload in release builds", + "status": "open", + "file_path": "decisions/questions-process.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-19", + "type": "confirmed", + "domain": "extensions", + "title": "Lua runtime as `ptyc`-peer supporter tool", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/extensions.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-19", + "type": "question", + "domain": "process", + "title": "(withdrawn)", + "status": "resolved", + "file_path": "decisions/questions-process.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-20", + "type": "confirmed", + "domain": "accessibility", + "title": "A11y is a Tier-0 contract", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/accessibility.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-20", + "type": "question", + "domain": "process", + "title": "Kernel DB service \u2014 namespaced SQL access?", + "status": "open", + "file_path": "decisions/questions-process.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-21", + "type": "confirmed", + "domain": "accessibility", + "title": "i18n is a Tier-0 contract (fframe pattern + locale-fallback chain)", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/accessibility.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-21", + "type": "question", + "domain": "architecture", + "title": "Pql absorbs planning vs keeps separate", + "status": "open", + "file_path": "decisions/questions-architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-22", + "type": "confirmed", + "domain": "accessibility", + "title": "WCAG-AA contrast gate on bundled themes", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/accessibility.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-22", + "type": "question", + "domain": "architecture", + "title": "Ticket persistence strategy", + "status": "open", + "file_path": "decisions/questions-architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-23", + "type": "confirmed", + "domain": "testing", + "title": "Test pyramid \u2014 seven layers", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/testing.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-23", + "type": "question", + "domain": "architecture", + "title": "SSH-remote development \u2014 run clide against a remote workspace", + "status": "open", + "file_path": "decisions/questions-architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-24", + "type": "confirmed", + "domain": "testing", + "title": "Golden tests \u2014 primitives only, Alchemist + Ahem", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/testing.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-25", + "type": "confirmed", + "domain": "testing", + "title": "Mocks \u2014 mocktail at IO, hand-rolled fakes for ChangeNotifiers", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/testing.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-25", + "type": "question", + "domain": "architecture", + "title": "Body text face \u2014 mono everywhere vs Josefin Sans UI + mono code", + "status": "open", + "file_path": "decisions/questions-architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-26", + "type": "confirmed", + "domain": "testing", + "title": "Web driver \u2014 raw Playwright + Flutter semantics", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/testing.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-26", + "type": "question", + "domain": "architecture", + "title": "Small screen layout (< 1000px)", + "status": "open", + "file_path": "decisions/questions-architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-27", + "type": "confirmed", + "domain": "testing", + "title": "Startup regression gate", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/testing.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-27", + "type": "question", + "domain": "architecture", + "title": "Two-editor split", + "status": "open", + "file_path": "decisions/questions-architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-28", + "type": "confirmed", + "domain": "testing", + "title": "Test organisation \u2014 mirror `lib/` in `test/`", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/testing.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-28", + "type": "question", + "domain": "architecture", + "title": "Terminal strip scope \u2014 shell only or logs/errors/tests", + "status": "open", + "file_path": "decisions/questions-architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-29", + "type": "confirmed", + "domain": "testing", + "title": "Pre-push gate \u2014 fast layer only", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/testing.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-29", + "type": "question", + "domain": "architecture", + "title": "Branch picker location", + "status": "open", + "file_path": "decisions/questions-architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-30", + "type": "confirmed", + "domain": "testing", + "title": "Tests are client-side only", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/testing.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-30", + "type": "question", + "domain": "architecture", + "title": "Focus behavior when editor is dirty and viewer is peeked", + "status": "open", + "file_path": "decisions/questions-architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-31", + "type": "confirmed", + "domain": "tooling", + "title": "Prefer-zero-deps, exact-pin", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/tooling.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "Q-31", + "type": "question", + "domain": "architecture", + "title": "XWayland fallback for frameless \u2014 proper Wayland protocol needed", + "status": "open", + "file_path": "decisions/questions-architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-32", + "type": "confirmed", + "domain": "tooling", + "title": "CI \u2014 Gitea primary, Linux-only runners, not yet activated", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/tooling.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-33", + "type": "confirmed", + "domain": "tooling", + "title": "Golden-output ignore pattern \u2014 `coverage.*` excludes output, not scripts", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/tooling.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-34", + "type": "confirmed", + "domain": "process", + "title": "Q&D record system", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/process.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-35", + "type": "confirmed", + "domain": "process", + "title": "Kanban / waterfall, not Scrum", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/process.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-36", + "type": "confirmed", + "domain": "process", + "title": "`.claude/` is committed project surface, managed through the IDE", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/process.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-37", + "type": "confirmed", + "domain": "process", + "title": "Commit conventions per git-commit skill", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/process.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-38", + "type": "confirmed", + "domain": "process", + "title": "Changelog discipline \u2014 Keep a Changelog 1.1.0", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/process.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-39", + "type": "confirmed", + "domain": "process", + "title": "Planning tooling lives in pql, not clide", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/process.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-40", + "type": "confirmed", + "domain": "process", + "title": "[SUPERSEDED] Python stopgap under `tools/scripts/plan`", + "status": "active", + "date": "2026-04-21", + "file_path": "decisions/process.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-41", + "type": "confirmed", + "domain": "architecture", + "title": "Claude panes \u2014 one primary per repo, tmux-backed", + "status": "active", + "date": "2026-04-22", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-42", + "type": "confirmed", + "domain": "tooling", + "title": "Dependencies documented in `licenses.yaml`", + "status": "active", + "date": "2026-04-22", + "file_path": "decisions/tooling.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-43", + "type": "confirmed", + "domain": "architecture", + "title": "Design handoff \u2014 adopt token palettes, reject Material wrapper", + "status": "active", + "date": "2026-04-22", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-44", + "type": "confirmed", + "domain": "architecture", + "title": "Four bundled themes \u2014 clide, midnight, paper, terminal", + "status": "active", + "date": "2026-04-22", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-45", + "type": "confirmed", + "domain": "architecture", + "title": "Syntax highlighting tokens in the theme pipeline", + "status": "active", + "date": "2026-04-22", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-46", + "type": "confirmed", + "domain": "extensions", + "title": "Core frame builtins vs shipped extensions boundary", + "status": "active", + "date": "2026-04-22", + "file_path": "decisions/extensions.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-47", + "type": "confirmed", + "domain": "architecture", + "title": "Interaction model \u2014 Claude-is-home layout", + "status": "active", + "date": "2026-04-22", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-48", + "type": "confirmed", + "domain": "architecture", + "title": "Chrome budget \u2014 no tabs, no breadcrumbs, keyboard-first", + "status": "active", + "date": "2026-04-22", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-49", + "type": "confirmed", + "domain": "architecture", + "title": "Editor mode \u2014 inline above Claude, viewer swap", + "status": "active", + "date": "2026-04-22", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-50", + "type": "confirmed", + "domain": "architecture", + "title": "Context auto-behavior \u2014 right panel reacts to Claude", + "status": "active", + "date": "2026-04-22", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-51", + "type": "confirmed", + "domain": "architecture", + "title": "Panel collapse \u2014 12px spine with badge", + "status": "active", + "date": "2026-04-22", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-52", + "type": "confirmed", + "domain": "architecture", + "title": "Focus mode \u2014 full-window takeover", + "status": "active", + "date": "2026-04-22", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-53", + "type": "confirmed", + "domain": "architecture", + "title": "State persistence across sessions", + "status": "active", + "date": "2026-04-22", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-54", + "type": "confirmed", + "domain": "architecture", + "title": "Keyboard map \u2014 canonical shortcuts", + "status": "active", + "date": "2026-04-22", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-55", + "type": "confirmed", + "domain": "architecture", + "title": "Claude pane internal tabs for multi-session", + "status": "active", + "date": "2026-04-23", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-56", + "type": "confirmed", + "domain": "architecture", + "title": "Dissolve daemon process; Flutter app hosts IPC server", + "status": "active", + "date": "2026-04-23", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-57", + "type": "confirmed", + "domain": "architecture", + "title": "Frameless custom chrome with per-column 24px hats", + "status": "active", + "date": "2026-04-23", + "file_path": "decisions/architecture.md", + "synced_at": "2026-04-23 09:37:12" + }, + { + "id": "D-58", + "type": "confirmed", + "domain": "tooling", + "title": "Format engines are adoptable dependencies", + "status": "active", + "date": "2026-04-23", + "file_path": "decisions/tooling.md", + "synced_at": "2026-04-23 09:37:12" + } + ], + "decision_refs": [ + { + "source_id": "D-1", + "target_id": "D-6", + "ref_type": "references", + "note": "**Rationale:** Same mental model as pql for the agent \u2014 one tool-use pattern covers both. No MCP runtime to host, authenticate, or keep in sync with client versions. User/Claude parity is easier ..." + }, + { + "source_id": "D-3", + "target_id": "D-4", + "ref_type": "references", + "note": "**Decision:** Two complementary rules. **(1) Wrap, don't duplicate.** Clide never re-implements backlinks, ranking, frontmatter parsing, or wikilink resolution for query purposes. If a capability i..." + }, + { + "source_id": "D-3", + "target_id": "D-39", + "ref_type": "references", + "note": "**Raised by:** Ported from the claudian lineage. Load-bearing for [D-39](process.md#d-039-planning-tooling-lives-in-pql)." + }, + { + "source_id": "D-4", + "target_id": "D-3", + "ref_type": "references", + "note": "**Decision:** One mechanism everywhere: the `ignore_files:` list in `.pql/config.yaml`. Ordered list of gitignore-shaped files; later entries win on per-pattern conflicts. pql defaults to `ignore_f..." + }, + { + "source_id": "D-5", + "target_id": "D-56", + "ref_type": "references", + "note": "**Amendment (2026-04-23):** The separate daemon process and two-package layout are dissolved per [D-56](#d-056-dissolve-daemon-process-flutter-app-hosts-ipc-server). Dart-core and ptyc-as-peer pri..." + }, + { + "source_id": "D-5", + "target_id": "R-2", + "ref_type": "references", + "note": "**Date:** 2026-04-20 (was ADR 0005; supersedes [R-2](rejected.md#r-002-go-sidecar))" + }, + { + "source_id": "D-6", + "target_id": "D-1", + "ref_type": "references", + "note": "**Context:** [D-1](#d-001-cli-first-not-mcp) established that Claude drives clide via a Bash CLI. That decided the *channel* \u2014 it did not define the *surface*. CLAUDE.md stated the rule colloqu..." + }, + { + "source_id": "D-6", + "target_id": "Q-1", + "ref_type": "references", + "note": "**Cost:** Replay-buffer memory per subsystem (cheap \u2014 most emit seldom). Back-pressure on firehose streams ([Q-2](questions-architecture.md#q-002-back-pressure-on-event-streams)), authorisation..." + }, + { + "source_id": "D-6", + "target_id": "Q-2", + "ref_type": "references", + "note": "**Cost:** Replay-buffer memory per subsystem (cheap \u2014 most emit seldom). Back-pressure on firehose streams ([Q-2](questions-architecture.md#q-002-back-pressure-on-event-streams)), authorisation..." + }, + { + "source_id": "D-6", + "target_id": "Q-3", + "ref_type": "references", + "note": "**Cost:** Replay-buffer memory per subsystem (cheap \u2014 most emit seldom). Back-pressure on firehose streams ([Q-2](questions-architecture.md#q-002-back-pressure-on-event-streams)), authorisation..." + }, + { + "source_id": "D-6", + "target_id": "Q-5", + "ref_type": "references", + "note": "**Rationale:** Surface is enumerable \u2014 adding a subsystem means adding a row and specifying verbs + events. Wire schema is versioned (`v: 1` starting point; compatibility breaks bump the major an..." + }, + { + "source_id": "D-7", + "target_id": "D-9", + "ref_type": "references", + "note": "**Rationale:** Clide is a Linux-primary desktop IDE with a custom theme pipeline and custom primitives (panels, tabs, panes, canvas). Material's implicit theming collides with [D-9](#d-009-three-..." + }, + { + "source_id": "D-7", + "target_id": "R-3", + "ref_type": "references", + "note": "**Cost:** We build and own every primitive; no `ElevatedButton` fallback. See [R-3](rejected.md#r-003-materialapp-root) and [R-7](rejected.md#r-007-cupertinoapp-root)." + }, + { + "source_id": "D-7", + "target_id": "R-7", + "ref_type": "references", + "note": "**Cost:** We build and own every primitive; no `ElevatedButton` fallback. See [R-3](rejected.md#r-003-materialapp-root) and [R-7](rejected.md#r-007-cupertinoapp-root)." + }, + { + "source_id": "D-9", + "target_id": "D-22", + "ref_type": "references", + "note": "**Cost:** Three layers to keep coherent per theme. Contrast gate ([D-22](accessibility.md#d-022-wcag-aa-contrast-gate-on-bundled-themes)) enforces the semantic layer on every bundled theme." + }, + { + "source_id": "D-10", + "target_id": "D-25", + "ref_type": "references", + "note": "**Rationale:** SDK-shipped, zero deps, trivial to fake in tests (hand-rolled fakes in [D-25](testing.md#d-025-mocks-mocktail-at-io-plus-hand-rolled-fakes)). Violates [D-31 prefer-zero-deps](tooli..." + }, + { + "source_id": "D-10", + "target_id": "D-31", + "ref_type": "references", + "note": "**Rationale:** SDK-shipped, zero deps, trivial to fake in tests (hand-rolled fakes in [D-25](testing.md#d-025-mocks-mocktail-at-io-plus-hand-rolled-fakes)). Violates [D-31 prefer-zero-deps](tooli..." + }, + { + "source_id": "D-10", + "target_id": "R-8", + "ref_type": "references", + "note": "**Rationale:** SDK-shipped, zero deps, trivial to fake in tests (hand-rolled fakes in [D-25](testing.md#d-025-mocks-mocktail-at-io-plus-hand-rolled-fakes)). Violates [D-31 prefer-zero-deps](tooli..." + }, + { + "source_id": "D-16", + "target_id": "D-19", + "ref_type": "references", + "note": "**Decision:** Bundled extensions (every `app/lib/builtin/`) are Dart \u2014 they link into the app binary. Third-party extensions (Tier 6) run in sandboxed Lua via the `ptyc`-peer Lua runtime (s..." + }, + { + "source_id": "D-18", + "target_id": "D-21", + "ref_type": "references", + "note": "**Decision:** Themes and extension manifests are YAML; i18n catalogues are JSON (fframe parity \u2014 see [D-21](accessibility.md#d-021-i18n-is-a-tier-0-contract))." + }, + { + "source_id": "D-21", + "target_id": "R-4", + "ref_type": "references", + "note": "**Rationale:** Flutter's `intl` + ARB codegen is inflexible for plugin-contributed catalogs (see [R-4](rejected.md#r-004-flutter-intl-and-arb-codegen)) \u2014 we need per-extension catalogs that mer..." + }, + { + "source_id": "D-23", + "target_id": "D-29", + "ref_type": "references", + "note": "**Cost:** Seven CI jobs; total wall time budgeted at < 15 min. Pre-push runs layers 1-4 (< 90 s \u2014 see [D-29](#d-029-pre-push-gate-fast-layer-only))." + }, + { + "source_id": "D-26", + "target_id": "D-20", + "ref_type": "references", + "note": "**Rationale:** Patrol adds a dependency for a capability we get from semantics + Playwright directly. Labels are the a11y tree we already contract to maintain ([D-20](accessibility.md#d-020-a11y-i..." + }, + { + "source_id": "D-35", + "target_id": "Q-22", + "ref_type": "references", + "note": "**Cost:** No natural \"sprint close\" event to sync shared state. See [Q-22](questions-process.md#q-022-ticket-persistence-strategy)." + }, + { + "source_id": "D-39", + "target_id": "D-3", + "ref_type": "references", + "note": "**Decision:** Planning subcommands (`decisions`, `ticket`, `plan`) land in pql's repo long-term. Clide consumes them via shell-out, matching [D-3](architecture.md)'s wrap-don't-duplicate rule for..." + }, + { + "source_id": "D-39", + "target_id": "D-40", + "ref_type": "references", + "note": "**Cost:** Planning features don't ship until pql catches up. Mitigated by [D-40](#d-040-python-stopgap-under-toolsscriptsplan). Gated by [Q-21](questions-process.md#q-021-pql-absorbs-planning-vs-..." + }, + { + "source_id": "D-39", + "target_id": "Q-21", + "ref_type": "references", + "note": "**Cost:** Planning features don't ship until pql catches up. Mitigated by [D-40](#d-040-python-stopgap-under-toolsscriptsplan). Gated by [Q-21](questions-process.md#q-021-pql-absorbs-planning-vs-..." + }, + { + "source_id": "D-39", + "target_id": "R-9", + "ref_type": "references", + "note": "**Rationale:** A terminal user or a user in VS Code / JetBrains still needs Q&D access. Binding planning tooling to clide-the-Flutter-app would cut them off from their own work \u2014 see [R-9](reje..." + }, + { + "source_id": "D-40", + "target_id": "R-10", + "ref_type": "references", + "note": "**Cost:** Python dep on contributors' machines (already present on most Linux dists). One time-limited tool to maintain. See [R-10](rejected.md#r-010-python-script-stopgap-at-toolingdb) for why `t..." + }, + { + "source_id": "D-40", + "target_id": "R-11", + "ref_type": "references", + "note": "**Sunset:** Delete the stopgap when pql ships `pql decisions sync | validate | list | show | claim | coverage` + `pql ticket new | list | show | status | assign | block | board` with feature parity..." + }, + { + "source_id": "D-41", + "target_id": "D-5", + "ref_type": "references", + "note": "**Cross-reference:** [`D-5`](#d-005-dart-core-sidecar-dissolved-ptyc-as-pql-peer) (ptyc as the spawn primitive tmux runs under), [`D-6`](#d-006-cli-and-event-surface-contract) (pane.\\* IPC surf..." + }, + { + "source_id": "D-41", + "target_id": "D-6", + "ref_type": "references", + "note": "**Cross-reference:** [`D-5`](#d-005-dart-core-sidecar-dissolved-ptyc-as-pql-peer) (ptyc as the spawn primitive tmux runs under), [`D-6`](#d-006-cli-and-event-surface-contract) (pane.\\* IPC surf..." + }, + { + "source_id": "D-41", + "target_id": "R-9", + "ref_type": "references", + "note": "**Cross-reference:** [`D-5`](#d-005-dart-core-sidecar-dissolved-ptyc-as-pql-peer) (ptyc as the spawn primitive tmux runs under), [`D-6`](#d-006-cli-and-event-surface-contract) (pane.\\* IPC surf..." + }, + { + "source_id": "D-42", + "target_id": "D-31", + "ref_type": "references", + "note": "**Rationale:** Complements [D-31](#d-031-prefer-zero-deps-exact-pin). Prefer-zero-deps is a *budget*; `licenses.yaml` is the *visible consequence*. An extra row in the About screen is a review-tim..." + }, + { + "source_id": "D-43", + "target_id": "D-7", + "ref_type": "references", + "note": "**Decision:** The claude.ai/design handoff (`docs/claude-design/`) delivers hi-fi mockups, interaction flows, a design system, and four theme palettes (clide, midnight, paper, terminal) as Dart fil..." + }, + { + "source_id": "D-43", + "target_id": "D-9", + "ref_type": "references", + "note": "**Cross-reference:** [D-7](#d-007-app-root-is-bare-widgetsapp), [D-9](#d-009-three-tier-theme-pipeline), [R-12](rejected.md#r-012-materialapp-wrapper-from-design-handoff)." + }, + { + "source_id": "D-43", + "target_id": "R-12", + "ref_type": "references", + "note": "**Cross-reference:** [D-7](#d-007-app-root-is-bare-widgetsapp), [D-9](#d-009-three-tier-theme-pipeline), [R-12](rejected.md#r-012-materialapp-wrapper-from-design-handoff)." + }, + { + "source_id": "D-44", + "target_id": "D-22", + "ref_type": "references", + "note": "**Cross-reference:** [D-43](#d-043-design-handoff-adopt-token-palettes-reject-material-wrapper), [D-22](accessibility.md#d-022-wcag-aa-contrast-gate-on-bundled-themes)." + }, + { + "source_id": "D-44", + "target_id": "D-43", + "ref_type": "references", + "note": "**Cross-reference:** [D-43](#d-043-design-handoff-adopt-token-palettes-reject-material-wrapper), [D-22](accessibility.md#d-022-wcag-aa-contrast-gate-on-bundled-themes)." + }, + { + "source_id": "D-45", + "target_id": "Q-15", + "ref_type": "references", + "note": "**Decision:** Add syntax-role colour tokens to `SurfaceTokens`: keyword, type, string, number, comment, method, punctuation. Each bundled theme defines these. The editor and diff views consume them..." + }, + { + "source_id": "D-47", + "target_id": "D-11", + "ref_type": "references", + "note": "**Decision:** The prompt bar is pinned to a fixed Y-position in the middle column; every other surface makes room *around* Claude \u2014 never on top, never pushing the prompt off-Y. Three hard rules:..." + }, + { + "source_id": "D-47", + "target_id": "D-48", + "ref_type": "references", + "note": "**Cross-reference:** [D-11](#d-011-panel-manager-is-kernel-layout-is-data-three-column-is-a-preset), [D-48](#d-048-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first), [D-49](#d-049-editor-mode..." + }, + { + "source_id": "D-47", + "target_id": "D-49", + "ref_type": "references", + "note": "**Cost:** The prompt bar invariant constrains future layout presets \u2014 any preset that repositions Claude must explicitly break this rule. Editor mode (see [D-49](#d-049-editor-mode-inline-above-..." + }, + { + "source_id": "D-48", + "target_id": "D-47", + "ref_type": "references", + "note": "**Cross-reference:** [D-47](#d-047-interaction-model-claude-is-home-layout), [D-49](#d-049-editor-mode-inline-above-claude-viewer-swap)." + }, + { + "source_id": "D-48", + "target_id": "D-49", + "ref_type": "references", + "note": "**Rationale:** Every pixel of chrome that isn't Claude is a tax on the \"Claude is home\" principle. Tabs and breadcrumbs are navigation affordances for a multi-buffer editor; clide's editor is a sec..." + }, + { + "source_id": "D-48", + "target_id": "Q-27", + "ref_type": "references", + "note": "**Decision:** Clide deletes classic IDE chrome: no buffer tabs, no breadcrumbs, no VS Code-style activity bar, no separate status bar row (merged into app strip). Total persistent chrome: 2 edge ar..." + }, + { + "source_id": "D-49", + "target_id": "D-47", + "ref_type": "references", + "note": "**Rationale:** The editor is not a primary surface \u2014 it's a temporary intervention. Claude's prompt bar must never move ([D-47](#d-047-interaction-model-claude-is-home-layout)), so the editor op..." + }, + { + "source_id": "D-49", + "target_id": "D-48", + "ref_type": "references", + "note": "**Cost:** Only one file in the editor at a time (no tabs per [D-48](#d-048-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first)). Power users wanting two files side-by-side must wait for split (se..." + }, + { + "source_id": "D-49", + "target_id": "Q-27", + "ref_type": "references", + "note": "**Cost:** Only one file in the editor at a time (no tabs per [D-48](#d-048-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first)). Power users wanting two files side-by-side must wait for split (se..." + }, + { + "source_id": "D-50", + "target_id": "D-47", + "ref_type": "references", + "note": "**Cross-reference:** [D-47](#d-047-interaction-model-claude-is-home-layout), [D-51](#d-051-panel-collapse-12px-spine-with-badge)." + }, + { + "source_id": "D-50", + "target_id": "D-51", + "ref_type": "references", + "note": "**Cross-reference:** [D-47](#d-047-interaction-model-claude-is-home-layout), [D-51](#d-051-panel-collapse-12px-spine-with-badge)." + }, + { + "source_id": "D-51", + "target_id": "D-47", + "ref_type": "references", + "note": "**Cross-reference:** [D-47](#d-047-interaction-model-claude-is-home-layout), [D-50](#d-050-context-auto-behavior-right-panel-reacts-to-claude)." + }, + { + "source_id": "D-51", + "target_id": "D-50", + "ref_type": "references", + "note": "**Cross-reference:** [D-47](#d-047-interaction-model-claude-is-home-layout), [D-50](#d-050-context-auto-behavior-right-panel-reacts-to-claude)." + }, + { + "source_id": "D-51", + "target_id": "D-53", + "ref_type": "references", + "note": "**Cost:** The spine replaces the current simple `setVisible(false)` toggle with a real collapsed-state widget. Collapse state must be persisted across sessions (see [D-53](#d-053-state-persistence..." + }, + { + "source_id": "D-52", + "target_id": "D-47", + "ref_type": "references", + "note": "**Cross-reference:** [D-47](#d-047-interaction-model-claude-is-home-layout), [D-53](#d-053-state-persistence-across-sessions)." + }, + { + "source_id": "D-52", + "target_id": "D-53", + "ref_type": "references", + "note": "**Cross-reference:** [D-47](#d-047-interaction-model-claude-is-home-layout), [D-53](#d-053-state-persistence-across-sessions)." + }, + { + "source_id": "D-53", + "target_id": "D-47", + "ref_type": "references", + "note": "**Cross-reference:** [D-47](#d-047-interaction-model-claude-is-home-layout), [D-51](#d-051-panel-collapse-12px-spine-with-badge), [D-52](#d-052-focus-mode-full-window-takeover)." + }, + { + "source_id": "D-53", + "target_id": "D-51", + "ref_type": "references", + "note": "**Cross-reference:** [D-47](#d-047-interaction-model-claude-is-home-layout), [D-51](#d-051-panel-collapse-12px-spine-with-badge), [D-52](#d-052-focus-mode-full-window-takeover)." + }, + { + "source_id": "D-53", + "target_id": "D-52", + "ref_type": "references", + "note": "**Cross-reference:** [D-47](#d-047-interaction-model-claude-is-home-layout), [D-51](#d-051-panel-collapse-12px-spine-with-badge), [D-52](#d-052-focus-mode-full-window-takeover)." + }, + { + "source_id": "D-54", + "target_id": "D-17", + "ref_type": "references", + "note": "**Cost:** Some shortcuts may conflict with OS-level bindings on specific Linux desktops; the keybinding resolver ([D-17](extensions.md#d-017-panels-are-extension-shaped-from-day-one)) allows user ..." + }, + { + "source_id": "D-54", + "target_id": "D-47", + "ref_type": "references", + "note": "**Cross-reference:** [D-47](#d-047-interaction-model-claude-is-home-layout), [D-48](#d-048-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first), [D-52](#d-052-focus-mode-full-window-takeover)." + }, + { + "source_id": "D-54", + "target_id": "D-48", + "ref_type": "references", + "note": "**Rationale:** These shortcuts follow the \"keyboard is the primary surface\" principle from [D-48](#d-048-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first). The set is minimal and covers all lay..." + }, + { + "source_id": "D-54", + "target_id": "D-52", + "ref_type": "references", + "note": "**Cross-reference:** [D-47](#d-047-interaction-model-claude-is-home-layout), [D-48](#d-048-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first), [D-52](#d-052-focus-mode-full-window-takeover)." + }, + { + "source_id": "D-54", + "target_id": "Q-26", + "ref_type": "references", + "note": "**Decision:** Canonical keyboard shortcuts (cross-platform, `\u2318` = `Ctrl` on Linux): `\u2318P` fuzzy find overlay; `\u2318\u21e71` / `\u2318\u21e73` collapse/expand left / right panel; `\u23181` / `\u23182` / `\u23183` f..." + }, + { + "source_id": "D-55", + "target_id": "D-41", + "ref_type": "references", + "note": "**Decision:** Multiple Claude sessions share the workspace as internal tabs inside the Claude pane header \u2014 not as workspace-level tabs (which would violate [D-48](#d-048-chrome-budget-no-tabs-n..." + }, + { + "source_id": "D-55", + "target_id": "D-47", + "ref_type": "references", + "note": "**Rationale:** The workspace is Claude's space ([D-47](#d-047-interaction-model-claude-is-home-layout)). Multiple Claude sessions are a Claude concern, not a workspace concern. Internal tabs keep ..." + }, + { + "source_id": "D-55", + "target_id": "D-48", + "ref_type": "references", + "note": "**Decision:** Multiple Claude sessions share the workspace as internal tabs inside the Claude pane header \u2014 not as workspace-level tabs (which would violate [D-48](#d-048-chrome-budget-no-tabs-n..." + }, + { + "source_id": "D-56", + "target_id": "D-1", + "ref_type": "references", + "note": "**Cross-reference:** [D-5](#d-005-dart-core-sidecar-dissolved-ptyc-as-pql-peer) (amended), [D-41](#d-041-claude-panes-one-primary-per-repo-tmux-backed) (tmux persistence), [D-1](#d-001-cli-fir..." + }, + { + "source_id": "D-56", + "target_id": "D-5", + "ref_type": "references", + "note": "**Amendment to [D-5](#d-005-dart-core-sidecar-dissolved-ptyc-as-pql-peer):** D-5's \"two execution modes of one Dart AOT binary\" premise assumed the daemon needed to outlive the app to preserve ..." + }, + { + "source_id": "D-56", + "target_id": "D-41", + "ref_type": "references", + "note": "**Decision:** The separate Dart daemon process (`clide --daemon`) and the two-package repo layout (`lib/` core + `app/` Flutter) are dissolved. The Flutter app moves to the repo root (one `pubspec...." + }, + { + "source_id": "D-57", + "target_id": "D-31", + "ref_type": "references", + "note": "**Rationale:** The GTK headerbar wastes 30+ vertical pixels and clashes with the custom theme. Per-column hats add zero net rows \u2014 they reuse the space each column header already occupied. Custom..." + }, + { + "source_id": "D-57", + "target_id": "D-47", + "ref_type": "references", + "note": "**Cross-reference:** [D-47](#d-047-interaction-model-claude-is-home-layout) (center hat always visible), [D-51](#d-051-panel-collapse-12px-spine-with-badge) (spine-cap behavior)." + }, + { + "source_id": "D-57", + "target_id": "D-51", + "ref_type": "references", + "note": "**Cross-reference:** [D-47](#d-047-interaction-model-claude-is-home-layout) (center hat always visible), [D-51](#d-051-panel-collapse-12px-spine-with-badge) (spine-cap behavior)." + }, + { + "source_id": "D-57", + "target_id": "Q-6", + "ref_type": "resolves", + "note": "**Resolves:** [Q-6](questions-architecture.md#q-006-window-chrome-native-frame-vs-frameless-custom)." + }, + { + "source_id": "D-58", + "target_id": "D-31", + "ref_type": "references", + "note": "**Amendment to D-31 (prefer-zero-deps):** D-31's \"prefer-zero-deps\" still applies \u2014 every new dependency needs justification. This record clarifies that format engines clear the justification b..." + }, + { + "source_id": "D-58", + "target_id": "D-42", + "ref_type": "references", + "note": "**Cross-reference:** [D-31](#d-031-prefer-zero-deps-exact-pin), [D-42](#d-042-dependencies-documented-in-licensesyaml)." + }, + { + "source_id": "Q-1", + "target_id": "D-6", + "ref_type": "references", + "note": "**Source:** ADR 0006 (migrated to [D-6](architecture.md))." + }, + { + "source_id": "Q-2", + "target_id": "D-6", + "ref_type": "references", + "note": "**Source:** ADR 0006 (migrated to [D-6](architecture.md))." + }, + { + "source_id": "Q-3", + "target_id": "D-6", + "ref_type": "references", + "note": "**Source:** ADR 0006 (migrated to [D-6](architecture.md))." + }, + { + "source_id": "Q-5", + "target_id": "D-6", + "ref_type": "references", + "note": "**Context:** Covered partially by [D-6](architecture.md)'s `v: 1` starting point; CLAUDE.md flags this as \"decide when the first real subcommand lands.\"" + }, + { + "source_id": "Q-6", + "target_id": "D-57", + "ref_type": "references", + "note": "**Status:** Resolved \u2192 [D-57](architecture.md#d-057-frameless-custom-chrome-with-per-column-24px-hats)" + }, + { + "source_id": "Q-8", + "target_id": "D-6", + "ref_type": "references", + "note": "**Question:** Should extensions contribute widgets (panels, tabs, status-bar items), subcommands (CLI verbs), or both? Both is the obvious answer but has a cost in API surface that must be designed..." + }, + { + "source_id": "Q-9", + "target_id": "D-19", + "ref_type": "references", + "note": "**Question:** Does the Lua supporter tool ([D-19](extensions.md#d-019-lua-runtime-as-ptyc-peer-supporter-tool)) bundle liblua source (build with the binary) or link system liblua (smaller binary, ..." + }, + { + "source_id": "Q-10", + "target_id": "Q-5", + "ref_type": "references", + "note": "**Question:** What's the manifest schema-version scheme and bump policy? Coupled with [Q-5](questions-architecture.md#q-005-ipc-wire-format-stability) (IPC wire format) \u2014 both want a versioning..." + }, + { + "source_id": "Q-12", + "target_id": "D-22", + "ref_type": "references", + "note": "**Question:** [D-22](accessibility.md#d-022-wcag-aa-contrast-gate-on-bundled-themes) gates contrast at build time. Do we also run axe-core against the WASM build in Playwright for runtime a11y iss..." + }, + { + "source_id": "Q-14", + "target_id": "D-31", + "ref_type": "references", + "note": "**Question:** fframe's pattern covers straight key\u2192string lookup with variable interpolation. Plurals, gendered forms, and ICU-style date formatting aren't in scope there. Do we add them to the i..." + }, + { + "source_id": "Q-16", + "target_id": "Q-15", + "ref_type": "references", + "note": "**Context:** If tree-sitter is the Tier-2 answer ([Q-15](#q-015-editor-tab-full-lsp-vs-tree-sitter-only)), grammar sourcing matters." + }, + { + "source_id": "Q-17", + "target_id": "R-6", + "ref_type": "references", + "note": "**Context:** We rejected Nerd-font glyphs ([R-6](rejected.md#r-006-nerd-font-glyph-icons)); something has to fill the gap." + }, + { + "source_id": "Q-19", + "target_id": "D-40", + "ref_type": "references", + "note": "**Note:** Earlier floated as \"ticket markdown mirror vs SQLite\" \u2014 no longer a split question. Markdown mirror is tracked in [Q-22](questions-architecture.md#q-022-ticket-persistence-strategy); S..." + }, + { + "source_id": "Q-19", + "target_id": "Q-22", + "ref_type": "references", + "note": "**Note:** Earlier floated as \"ticket markdown mirror vs SQLite\" \u2014 no longer a split question. Markdown mirror is tracked in [Q-22](questions-architecture.md#q-022-ticket-persistence-strategy); S..." + }, + { + "source_id": "Q-20", + "target_id": "D-12", + "ref_type": "references", + "note": "**Question:** Do extensions get namespaced SQL access to `.clide/clide.db` (tables prefixed `ext__\u2026`) or stay on the `kernel.settings` key/value facade? Admission-level question ([D-12](arch..." + }, + { + "source_id": "Q-21", + "target_id": "D-3", + "ref_type": "references", + "note": "**Question:** Three shapes for planning tooling's long-term home: (A) Pql absorbs planning \u2014 `pql decisions \u2026` + `pql ticket \u2026` subcommands; clide shells out. (B) Clide absorbs pql \u2014 revers..." + }, + { + "source_id": "Q-21", + "target_id": "D-39", + "ref_type": "references", + "note": "**Context:** User is leaning (A). This plan assumes (A) without committing. If (A) doesn't land, [D-40](process.md#d-040-python-stopgap-under-toolsscriptsplan)'s sunset condition changes. Gates al..." + }, + { + "source_id": "Q-21", + "target_id": "D-40", + "ref_type": "references", + "note": "**Context:** User is leaning (A). This plan assumes (A) without committing. If (A) doesn't land, [D-40](process.md#d-040-python-stopgap-under-toolsscriptsplan)'s sunset condition changes. Gates al..." + }, + { + "source_id": "Q-21", + "target_id": "R-9", + "ref_type": "references", + "note": "**Context:** User is leaning (A). This plan assumes (A) without committing. If (A) doesn't land, [D-40](process.md#d-040-python-stopgap-under-toolsscriptsplan)'s sunset condition changes. Gates al..." + }, + { + "source_id": "Q-22", + "target_id": "Q-21", + "ref_type": "references", + "note": "**Question:** Once [Q-21](#q-021-pql-absorbs-planning-vs-keeps-separate) resolves in favour of (A), how do tickets handle shared team state? (1) Never commit (per-dev, ephemeral \u2014 works for solo..." + }, + { + "source_id": "Q-23", + "target_id": "D-5", + "ref_type": "references", + "note": "**Context:** Surfaced 2026-04-22 during Tier-1 planning. Not a Tier 1 concern \u2014 terminal + Claude panes land local-first \u2014 but the daemon/IPC seam decisions (notably `D-5` and `D-6`) constr..." + }, + { + "source_id": "Q-23", + "target_id": "D-6", + "ref_type": "references", + "note": "**Context:** Surfaced 2026-04-22 during Tier-1 planning. Not a Tier 1 concern \u2014 terminal + Claude panes land local-first \u2014 but the daemon/IPC seam decisions (notably `D-5` and `D-6`) constr..." + }, + { + "source_id": "Q-27", + "target_id": "D-48", + "ref_type": "references", + "note": "**Context:** [D-48](architecture.md#d-048-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first) deletes buffer tabs. A two-editor split would be the only way to compare files side-by-side without u..." + }, + { + "source_id": "Q-30", + "target_id": "D-47", + "ref_type": "references", + "note": "**Context:** This intersects [D-47](architecture.md#d-047-interaction-model-claude-is-home-layout) (Claude is home) and [D-49](architecture.md#d-049-editor-mode-inline-above-claude-viewer-swap) (..." + }, + { + "source_id": "Q-30", + "target_id": "D-49", + "ref_type": "references", + "note": "**Context:** This intersects [D-47](architecture.md#d-047-interaction-model-claude-is-home-layout) (Claude is home) and [D-49](architecture.md#d-049-editor-mode-inline-above-claude-viewer-swap) (..." + }, + { + "source_id": "Q-31", + "target_id": "D-57", + "ref_type": "references", + "note": "**Question:** The frameless window (D-57) currently forces `GDK_BACKEND=x11` because GTK3 doesn't implement the `xdg-decoration` Wayland protocol and KWin ignores `gtk_window_set_decorated(FALSE)`..." + }, + { + "source_id": "R-2", + "target_id": "D-5", + "ref_type": "references", + "note": "**Rejected:** 2026-04-20 (was ADR 0002; superseded by [D-5](architecture.md#d-005-dart-core-ptyc-peer))" + }, + { + "source_id": "R-3", + "target_id": "D-7", + "ref_type": "references", + "note": "**Cross-reference:** [D-7](architecture.md#d-007-app-root-is-bare-widgetsapp)" + }, + { + "source_id": "R-3", + "target_id": "D-9", + "ref_type": "references", + "note": "**Reason:** Dragged in Material theming, default icons, and platform chrome that fought the custom three-tier theme pipeline ([D-9](architecture.md#d-009-three-tier-theme-pipeline)). Every bundle..." + }, + { + "source_id": "R-4", + "target_id": "D-21", + "ref_type": "references", + "note": "**Cross-reference:** [D-21](accessibility.md#d-021-i18n-is-a-tier-0-contract)" + }, + { + "source_id": "R-5", + "target_id": "D-26", + "ref_type": "references", + "note": "**Cross-reference:** [D-26](testing.md#d-026-web-driver-raw-playwright-plus-flutter-semantics)" + }, + { + "source_id": "R-5", + "target_id": "D-31", + "ref_type": "references", + "note": "**Reason:** Adds a dependency (violates [D-31](tooling.md#d-031-prefer-zero-deps-exact-pin)) for a capability we get from Playwright + Flutter's own semantics tree. Patrol's value proposition (nat..." + }, + { + "source_id": "R-6", + "target_id": "Q-17", + "ref_type": "references", + "note": "**Cross-reference:** [Q-17](questions-process.md#q-017-icon-set-growth)" + }, + { + "source_id": "R-7", + "target_id": "D-7", + "ref_type": "references", + "note": "**Cross-reference:** [D-7](architecture.md#d-007-app-root-is-bare-widgetsapp)" + }, + { + "source_id": "R-7", + "target_id": "R-3", + "ref_type": "references", + "note": "**Reason:** iOS-opinionated; wrong shell for a Linux-primary desktop IDE. Same theming-collision problem as [R-3](#r-003-materialapp-root)." + }, + { + "source_id": "R-8", + "target_id": "D-10", + "ref_type": "references", + "note": "**Cross-reference:** [D-10](architecture.md#d-010-state-management-changenotifier)" + }, + { + "source_id": "R-8", + "target_id": "D-31", + "ref_type": "references", + "note": "**Reason:** Violates [D-31](tooling.md#d-031-prefer-zero-deps-exact-pin). `ChangeNotifier` + `ListenableBuilder` ship in the SDK, fake trivially, and cover the state model we need. The ergonomic w..." + }, + { + "source_id": "R-9", + "target_id": "D-39", + "ref_type": "references", + "note": "**Reason:** Earlier in the planning session the assumption was \"clide owns Dart subcommands for decisions + tickets.\" That breaks the day a contributor works in a terminal or in VS Code / JetBrains..." + }, + { + "source_id": "R-10", + "target_id": "D-40", + "ref_type": "references", + "note": "**Reason:** Location, not language. Settled-reach puts scripts at `tooling/db/` \u2014 copying that path here creates a script-pollution problem: every project using the pattern commits its own copy. ..." + }, + { + "source_id": "R-11", + "target_id": "D-40", + "ref_type": "references", + "note": "**Cross-reference:** [D-40](process.md#d-040-python-stopgap-under-toolsscriptsplan)" + }, + { + "source_id": "R-12", + "target_id": "D-7", + "ref_type": "references", + "note": "**Reason:** The design handoff delivers theme files as `MaterialApp`/`ThemeData` Dart classes. This is the delivery format of claude.ai/design, not a design intent. Adopting Material's widget syste..." + }, + { + "source_id": "R-12", + "target_id": "D-43", + "ref_type": "references", + "note": "**Cross-reference:** [D-43](architecture.md#d-043-design-handoff-adopt-token-palettes-reject-material-wrapper)" + } + ], + "tickets": [ + { + "id": "T-1", + "type": "task", + "title": "validate pql init installation cycle", + "status": "done", + "priority": "medium", + "decision_ref": "D-39", + "created_at": "2026-04-22 09:41:51", + "updated_at": "2026-04-22 11:45:21" + }, + { + "id": "T-2", + "type": "epic", + "title": "Tier 0 \u2014 app + daemon handshake, empty IDE shell", + "status": "done", + "priority": "medium", + "decision_ref": "D-7", + "created_at": "2026-04-22 11:45:26", + "updated_at": "2026-04-22 11:46:33" + }, + { + "id": "T-3", + "type": "epic", + "title": "Tier 1 \u2014 Claude in xterm pane, PTY, session persistence", + "status": "done", + "priority": "medium", + "decision_ref": "D-41", + "created_at": "2026-04-22 11:45:41", + "updated_at": "2026-04-22 11:46:37" + }, + { + "id": "T-4", + "type": "epic", + "title": "Tier 2 \u2014 pane model, editor, active-file awareness, CLI", + "status": "done", + "priority": "medium", + "decision_ref": "D-6", + "created_at": "2026-04-22 11:45:41", + "updated_at": "2026-04-22 11:46:44" + }, + { + "id": "T-5", + "type": "epic", + "title": "Tier 3 \u2014 git panel, diff tab, clide git CLI", + "status": "done", + "priority": "medium", + "decision_ref": "D-13", + "created_at": "2026-04-22 11:45:41", + "updated_at": "2026-04-22 11:46:48" + }, + { + "id": "T-6", + "type": "epic", + "title": "Tier 4 \u2014 pql integration: query panel, file tree, backlinks, problems", + "status": "done", + "priority": "medium", + "decision_ref": "D-3", + "created_at": "2026-04-22 11:45:41", + "updated_at": "2026-04-22 12:03:54" + }, + { + "id": "T-7", + "type": "epic", + "title": "Tier 5 \u2014 canvas and graph view", + "status": "backlog", + "priority": "medium", + "decision_ref": "D-17", + "created_at": "2026-04-22 11:45:41", + "updated_at": "2026-04-22 11:45:41" + }, + { + "id": "T-8", + "type": "epic", + "title": "Tier 6 \u2014 extension API, settings, theming, distributable builds", + "status": "backlog", + "priority": "medium", + "decision_ref": "D-15", + "created_at": "2026-04-22 11:45:41", + "updated_at": "2026-04-22 11:45:41" + }, + { + "id": "T-9", + "type": "task", + "title": "Q&D record system + ADR migration", + "status": "done", + "priority": "medium", + "decision_ref": "D-34", + "created_at": "2026-04-22 11:47:02", + "updated_at": "2026-04-22 11:47:11" + }, + { + "id": "T-10", + "type": "task", + "title": "Python stopgap tooling", + "status": "done", + "priority": "medium", + "decision_ref": "D-40", + "created_at": "2026-04-22 11:47:02", + "updated_at": "2026-04-22 11:47:11" + }, + { + "id": "T-11", + "type": "task", + "title": "pql skill install + stopgap removal", + "status": "done", + "priority": "medium", + "decision_ref": "D-39", + "created_at": "2026-04-22 11:47:02", + "updated_at": "2026-04-22 11:47:11" + }, + { + "id": "T-12", + "type": "task", + "title": "bundled dependency licensing", + "status": "done", + "priority": "medium", + "decision_ref": "D-42", + "created_at": "2026-04-22 11:47:03", + "updated_at": "2026-04-22 11:47:11" + }, + { + "id": "T-13", + "type": "task", + "title": "pql query panel \u2014 sidebar tab for vault queries", + "status": "done", + "priority": "medium", + "decision_ref": "D-3", + "created_at": "2026-04-22 11:47:18", + "updated_at": "2026-04-22 12:03:48" + }, + { + "id": "T-14", + "type": "task", + "title": "pql-backed file tree \u2014 replace or augment files.ls with pql files", + "status": "done", + "priority": "medium", + "decision_ref": "D-3", + "created_at": "2026-04-22 11:47:18", + "updated_at": "2026-04-22 12:03:48" + }, + { + "id": "T-15", + "type": "task", + "title": "backlinks panel \u2014 show inbound links to active file", + "status": "done", + "priority": "medium", + "decision_ref": "D-3", + "created_at": "2026-04-22 11:47:18", + "updated_at": "2026-04-22 12:03:48" + }, + { + "id": "T-16", + "type": "task", + "title": "problems panel \u2014 surface pql doctor + validate warnings", + "status": "done", + "priority": "medium", + "decision_ref": "D-3", + "created_at": "2026-04-22 11:47:18", + "updated_at": "2026-04-22 12:03:48" + }, + { + "id": "T-17", + "type": "task", + "title": "add dart doc generation to CI", + "status": "backlog", + "priority": "medium", + "decision_ref": "D-32", + "created_at": "2026-04-22 13:26:29", + "updated_at": "2026-04-22 13:26:29" + }, + { + "id": "T-18", + "type": "task", + "title": "audit error handling in PTY and IPC critical paths", + "status": "backlog", + "priority": "medium", + "decision_ref": "D-5", + "created_at": "2026-04-22 13:26:29", + "updated_at": "2026-04-22 13:26:29" + }, + { + "id": "T-19", + "type": "task", + "title": "translate four design themes into YAML theme files", + "status": "done", + "priority": "medium", + "decision_ref": "D-44", + "created_at": "2026-04-22 14:08:40", + "updated_at": "2026-04-22 14:30:05" + }, + { + "id": "T-20", + "type": "task", + "title": "expand SurfaceTokens with syntax highlighting roles", + "status": "done", + "priority": "medium", + "decision_ref": "D-45", + "created_at": "2026-04-22 14:08:40", + "updated_at": "2026-04-22 14:30:05" + }, + { + "id": "T-21", + "type": "task", + "title": "implement welcome screen per hi-fi design", + "status": "backlog", + "priority": "medium", + "decision_ref": "D-43", + "created_at": "2026-04-22 14:08:40", + "updated_at": "2026-04-22 14:08:40" + }, + { + "id": "T-22", + "type": "task", + "title": "multi-buffer editor tabs", + "status": "cancelled", + "priority": "medium", + "decision_ref": "D-6", + "created_at": "2026-04-22 14:08:40", + "updated_at": "2026-04-22 20:34:17" + }, + { + "id": "T-23", + "type": "task", + "title": "wire command palette keybinding", + "status": "backlog", + "priority": "medium", + "decision_ref": "D-6", + "created_at": "2026-04-22 14:08:40", + "updated_at": "2026-04-22 14:08:40" + }, + { + "id": "T-24", + "type": "task", + "title": "secondary Claude pane UI wiring", + "status": "backlog", + "priority": "medium", + "decision_ref": "D-41", + "created_at": "2026-04-22 14:08:40", + "updated_at": "2026-04-22 14:08:40" + }, + { + "id": "T-25", + "type": "task", + "title": "CI reproducible build for libtree-sitter.so", + "description": "BUILD.md at app/native/linux-x64/ has TODO checklist: build from pinned source SHA in CI, record SHA-256, cross-compile for macOS (aarch64, x86_64) and Windows (x86_64). Currently built on contributor machine.", + "status": "backlog", + "priority": "medium", + "created_at": "2026-04-22 20:14:24", + "updated_at": "2026-04-22 20:14:24" + }, + { + "id": "T-26", + "type": "task", + "title": "web-tree-sitter via JS interop for browser builds", + "description": "Conditional import behind TreeSitterService: native impl uses dart:ffi to libtree-sitter.so, web impl uses dart:js_interop to web-tree-sitter (official emscripten build from tree-sitter org). Same grammar .wasm files on both platforms. Vendor web-tree-sitter .wasm + JS glue as Flutter web assets, pinned version, added to licenses.yaml.", + "status": "backlog", + "priority": "medium", + "created_at": "2026-04-22 20:14:33", + "updated_at": "2026-04-22 20:14:33" + }, + { + "id": "T-27", + "type": "task", + "title": "migrate shipped extensions from builtin/ to extensions/ per D-46", + "description": "D-46 defines the boundary: content extensions (editor, claude, claude-control, markdown, diff, git-ui, pql, canvas, graph, decisions, tickets, todos, problems) move from app/lib/builtin/ to app/lib/extensions/. Incremental \u2014 one at a time, each behind a working build. Extension contract must support bundled Dart extension as a first-class category.", + "status": "backlog", + "priority": "medium", + "created_at": "2026-04-22 20:14:45", + "updated_at": "2026-04-22 20:14:45" + }, + { + "id": "T-28", + "type": "task", + "title": "feed POLICY.md rules into D-records", + "description": "POLICY.md (added 2026-04-22) defines rules for: no-network-on-default-launch-path, dependency vetting checklist, vendored binary rebuild process, telemetry prohibition, license compatibility matrix. These need corresponding D-records in decisions/ so the architectural decision trail is complete. Some may overlap with or supersede existing D-records.", + "status": "backlog", + "priority": "medium", + "created_at": "2026-04-22 20:14:52", + "updated_at": "2026-04-22 20:14:52" + }, + { + "id": "T-29", + "type": "story", + "title": "editor respects .editorconfig settings", + "description": "When a file is opened in the editor, read .editorconfig from the workspace root and apply: indent_style, indent_size, max_line_length (ruler/wrap guide), end_of_line, trim_trailing_whitespace, insert_final_newline. Parse the INI format ourselves (small, no dep). Glob matching per the EditorConfig spec.", + "status": "backlog", + "priority": "medium", + "created_at": "2026-04-22 20:17:26", + "updated_at": "2026-04-22 20:17:26" + }, + { + "id": "T-30", + "type": "story", + "title": "Implement 12px collapse spine for side panels", + "description": "Replace current visibility toggle with a 12px spine widget: rotated label, paper-2 bg, inner-edge border, click-to-expand, badge dot with count for pending context.", + "status": "done", + "priority": "high", + "decision_ref": "D-51", + "created_at": "2026-04-22 20:33:53", + "updated_at": "2026-04-22 20:38:51" + }, + { + "id": "T-31", + "type": "story", + "title": "Implement focus mode (full-window takeover)", + "description": "Focus mode via double-click header, hover \u26f6 glyph, or \u2318. shortcut. Snapshot LayoutArrangement on enter, restore on Esc. Show Esc hint in header.", + "status": "done", + "priority": "medium", + "decision_ref": "D-52", + "created_at": "2026-04-22 20:33:56", + "updated_at": "2026-04-22 20:38:51" + }, + { + "id": "T-32", + "type": "story", + "title": "Persist layout state across sessions", + "description": "Save collapse state, active sections, pql pane state, editor split ratio, and fuzzy find recent picks to .clide/settings.yaml via SettingsStore. Restore on boot.", + "status": "done", + "priority": "medium", + "decision_ref": "D-53", + "created_at": "2026-04-22 20:33:58", + "updated_at": "2026-04-22 21:02:29" + }, + { + "id": "T-33", + "type": "story", + "title": "Wire canonical keyboard shortcuts from interaction model", + "description": "Add commands and keybindings: \u2318\u21e71/\u2318\u21e73 collapse, \u23181/\u23182/\u23183 focus panel, \u2318. focus mode, \u23251-5 left section switch, \u2318E editor, \u2318W close editor, Esc dismiss.", + "status": "done", + "priority": "high", + "decision_ref": "D-54", + "created_at": "2026-04-22 20:34:01", + "updated_at": "2026-04-22 20:38:51" + }, + { + "id": "T-34", + "type": "story", + "title": "Add icon rail to right panel (context section switcher)", + "description": "Right panel currently has no bottom icon rail. Add one matching the left sidebar rail pattern: viewer, pql graph, links, images as section tabs.", + "status": "done", + "priority": "medium", + "decision_ref": "D-47", + "created_at": "2026-04-22 20:34:03", + "updated_at": "2026-04-22 20:38:51" + }, + { + "id": "T-35", + "type": "story", + "title": "Implement editor-above-Claude mode in middle column", + "description": "\u2318E opens editor above Claude with draggable divider, 30-40% height. \u2318W closes. Viewer \u2194 editor swap: \u270e promotes to editor, \ud83d\udc41 demotes to viewer. Prompt bar Y stays fixed.", + "status": "done", + "priority": "medium", + "decision_ref": "D-49", + "created_at": "2026-04-22 20:34:06", + "updated_at": "2026-04-22 20:56:58" + }, + { + "id": "T-36", + "type": "story", + "title": "Context auto-behavior: right panel reacts to Claude references", + "description": "Parse file references from Claude output. Swap viewer content when right panel is open. Badge on spine when collapsed. Live-sync viewer when editing .md.", + "status": "backlog", + "priority": "low", + "decision_ref": "D-50", + "created_at": "2026-04-22 20:34:09", + "updated_at": "2026-04-22 20:34:09" + }, + { + "id": "T-37", + "type": "task", + "title": "Contribute tickets & decisions tabs to sidebar", + "description": "The decisions and tickets shipped extensions exist as stubs but don't contribute TabContributions to Slots.sidebar yet. Spec lists them as the default left-panel section.", + "status": "done", + "priority": "medium", + "decision_ref": "D-47", + "created_at": "2026-04-22 21:03:40", + "updated_at": "2026-04-22 21:16:41" + }, + { + "id": "T-38", + "type": "task", + "title": "Contribute markdown viewer tab to context panel", + "description": "The markdown shipped extension exists as a stub but doesn't contribute a TabContribution to Slots.contextPanel. Spec lists viewer as the primary right-panel section.", + "status": "done", + "priority": "medium", + "decision_ref": "D-47", + "created_at": "2026-04-22 21:03:40", + "updated_at": "2026-04-22 21:16:41" + }, + { + "id": "T-39", + "type": "task", + "title": "Contribute graph view tab to context panel", + "description": "The graph shipped extension exists as a stub but doesn't contribute a TabContribution to Slots.contextPanel. Spec lists pql graph as a right-panel section.", + "status": "done", + "priority": "medium", + "decision_ref": "D-47", + "created_at": "2026-04-22 21:03:40", + "updated_at": "2026-04-22 21:16:41" + }, + { + "id": "T-40", + "type": "task", + "title": "Add PRs tab to sidebar", + "description": "Spec lists PRs as a left-panel section (icon rail position 5). No extension exists yet.", + "status": "backlog", + "priority": "low", + "decision_ref": "D-47", + "created_at": "2026-04-22 21:03:40", + "updated_at": "2026-04-22 21:03:40" + }, + { + "id": "T-41", + "type": "task", + "title": "Add images tab to context panel", + "description": "Spec lists images as a right-panel section. No extension exists yet.", + "status": "backlog", + "priority": "low", + "decision_ref": "D-47", + "created_at": "2026-04-22 21:03:40", + "updated_at": "2026-04-22 21:03:40" + }, + { + "id": "T-42", + "type": "task", + "title": "Decide diff view placement", + "description": "Diff view currently contributes to workspace. Spec doesn't place it. Likely belongs in context panel (viewer) or as an editor overlay. Depends on Q-27.", + "status": "backlog", + "priority": "low", + "decision_ref": "D-48", + "created_at": "2026-04-22 21:03:40", + "updated_at": "2026-04-22 21:03:40" + }, + { + "id": "T-43", + "type": "story", + "title": "Ship clide tmux.conf for Claude pane sessions", + "description": "Bundle a clide-specific tmux.conf (no status bar, large scrollback, zero escape delay, 256color passthrough). Claude pane spawn passes -f to use it. Includes bottom-anchoring via initial escape sequence or shell profile trick after attach.", + "status": "backlog", + "priority": "high", + "decision_ref": "D-41", + "created_at": "2026-04-22 22:01:13", + "updated_at": "2026-04-22 22:01:13" + } + ], + "ticket_deps": null, + "ticket_labels": null, + "history": [ + { + "ticket_id": "T-1", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 09:42:11" + }, + { + "ticket_id": "T-1", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 09:42:11" + }, + { + "ticket_id": "T-1", + "field": "status", + "old_value": "in_progress", + "new_value": "review", + "changed_at": "2026-04-22 11:45:21" + }, + { + "ticket_id": "T-1", + "field": "status", + "old_value": "review", + "new_value": "done", + "changed_at": "2026-04-22 11:45:21" + }, + { + "ticket_id": "T-2", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 11:46:12" + }, + { + "ticket_id": "T-2", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 11:46:16" + }, + { + "ticket_id": "T-2", + "field": "status", + "old_value": "in_progress", + "new_value": "review", + "changed_at": "2026-04-22 11:46:26" + }, + { + "ticket_id": "T-2", + "field": "status", + "old_value": "review", + "new_value": "done", + "changed_at": "2026-04-22 11:46:33" + }, + { + "ticket_id": "T-3", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 11:46:37" + }, + { + "ticket_id": "T-3", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 11:46:37" + }, + { + "ticket_id": "T-3", + "field": "status", + "old_value": "in_progress", + "new_value": "review", + "changed_at": "2026-04-22 11:46:37" + }, + { + "ticket_id": "T-3", + "field": "status", + "old_value": "review", + "new_value": "done", + "changed_at": "2026-04-22 11:46:37" + }, + { + "ticket_id": "T-4", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 11:46:44" + }, + { + "ticket_id": "T-4", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 11:46:44" + }, + { + "ticket_id": "T-4", + "field": "status", + "old_value": "in_progress", + "new_value": "review", + "changed_at": "2026-04-22 11:46:44" + }, + { + "ticket_id": "T-4", + "field": "status", + "old_value": "review", + "new_value": "done", + "changed_at": "2026-04-22 11:46:44" + }, + { + "ticket_id": "T-5", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 11:46:48" + }, + { + "ticket_id": "T-5", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 11:46:48" + }, + { + "ticket_id": "T-5", + "field": "status", + "old_value": "in_progress", + "new_value": "review", + "changed_at": "2026-04-22 11:46:48" + }, + { + "ticket_id": "T-5", + "field": "status", + "old_value": "review", + "new_value": "done", + "changed_at": "2026-04-22 11:46:48" + }, + { + "ticket_id": "T-6", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 11:46:52" + }, + { + "ticket_id": "T-9", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 11:47:11" + }, + { + "ticket_id": "T-9", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 11:47:11" + }, + { + "ticket_id": "T-9", + "field": "status", + "old_value": "in_progress", + "new_value": "review", + "changed_at": "2026-04-22 11:47:11" + }, + { + "ticket_id": "T-9", + "field": "status", + "old_value": "review", + "new_value": "done", + "changed_at": "2026-04-22 11:47:11" + }, + { + "ticket_id": "T-10", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 11:47:11" + }, + { + "ticket_id": "T-10", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 11:47:11" + }, + { + "ticket_id": "T-10", + "field": "status", + "old_value": "in_progress", + "new_value": "review", + "changed_at": "2026-04-22 11:47:11" + }, + { + "ticket_id": "T-10", + "field": "status", + "old_value": "review", + "new_value": "done", + "changed_at": "2026-04-22 11:47:11" + }, + { + "ticket_id": "T-11", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 11:47:11" + }, + { + "ticket_id": "T-11", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 11:47:11" + }, + { + "ticket_id": "T-11", + "field": "status", + "old_value": "in_progress", + "new_value": "review", + "changed_at": "2026-04-22 11:47:11" + }, + { + "ticket_id": "T-11", + "field": "status", + "old_value": "review", + "new_value": "done", + "changed_at": "2026-04-22 11:47:11" + }, + { + "ticket_id": "T-12", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 11:47:11" + }, + { + "ticket_id": "T-12", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 11:47:11" + }, + { + "ticket_id": "T-12", + "field": "status", + "old_value": "in_progress", + "new_value": "review", + "changed_at": "2026-04-22 11:47:11" + }, + { + "ticket_id": "T-12", + "field": "status", + "old_value": "review", + "new_value": "done", + "changed_at": "2026-04-22 11:47:11" + }, + { + "ticket_id": "T-6", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 11:52:59" + }, + { + "ticket_id": "T-13", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 11:52:59" + }, + { + "ticket_id": "T-14", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 11:52:59" + }, + { + "ticket_id": "T-15", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 11:52:59" + }, + { + "ticket_id": "T-16", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 11:52:59" + }, + { + "ticket_id": "T-13", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 12:03:48" + }, + { + "ticket_id": "T-13", + "field": "status", + "old_value": "in_progress", + "new_value": "review", + "changed_at": "2026-04-22 12:03:48" + }, + { + "ticket_id": "T-13", + "field": "status", + "old_value": "review", + "new_value": "done", + "changed_at": "2026-04-22 12:03:48" + }, + { + "ticket_id": "T-14", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 12:03:48" + }, + { + "ticket_id": "T-14", + "field": "status", + "old_value": "in_progress", + "new_value": "review", + "changed_at": "2026-04-22 12:03:48" + }, + { + "ticket_id": "T-14", + "field": "status", + "old_value": "review", + "new_value": "done", + "changed_at": "2026-04-22 12:03:48" + }, + { + "ticket_id": "T-15", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 12:03:48" + }, + { + "ticket_id": "T-15", + "field": "status", + "old_value": "in_progress", + "new_value": "review", + "changed_at": "2026-04-22 12:03:48" + }, + { + "ticket_id": "T-15", + "field": "status", + "old_value": "review", + "new_value": "done", + "changed_at": "2026-04-22 12:03:48" + }, + { + "ticket_id": "T-16", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 12:03:48" + }, + { + "ticket_id": "T-16", + "field": "status", + "old_value": "in_progress", + "new_value": "review", + "changed_at": "2026-04-22 12:03:48" + }, + { + "ticket_id": "T-16", + "field": "status", + "old_value": "review", + "new_value": "done", + "changed_at": "2026-04-22 12:03:48" + }, + { + "ticket_id": "T-6", + "field": "status", + "old_value": "in_progress", + "new_value": "review", + "changed_at": "2026-04-22 12:03:54" + }, + { + "ticket_id": "T-6", + "field": "status", + "old_value": "review", + "new_value": "done", + "changed_at": "2026-04-22 12:03:54" + }, + { + "ticket_id": "T-19", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 14:16:39" + }, + { + "ticket_id": "T-19", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 14:16:39" + }, + { + "ticket_id": "T-20", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 14:16:39" + }, + { + "ticket_id": "T-20", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 14:16:39" + }, + { + "ticket_id": "T-19", + "field": "status", + "old_value": "in_progress", + "new_value": "review", + "changed_at": "2026-04-22 14:30:05" + }, + { + "ticket_id": "T-19", + "field": "status", + "old_value": "review", + "new_value": "done", + "changed_at": "2026-04-22 14:30:05" + }, + { + "ticket_id": "T-20", + "field": "status", + "old_value": "in_progress", + "new_value": "review", + "changed_at": "2026-04-22 14:30:05" + }, + { + "ticket_id": "T-20", + "field": "status", + "old_value": "review", + "new_value": "done", + "changed_at": "2026-04-22 14:30:05" + }, + { + "ticket_id": "T-22", + "field": "status", + "old_value": "backlog", + "new_value": "cancelled", + "changed_at": "2026-04-22 20:34:17" + }, + { + "ticket_id": "T-30", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 20:38:42" + }, + { + "ticket_id": "T-31", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 20:38:42" + }, + { + "ticket_id": "T-33", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 20:38:42" + }, + { + "ticket_id": "T-34", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 20:38:42" + }, + { + "ticket_id": "T-30", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 20:38:49" + }, + { + "ticket_id": "T-31", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 20:38:49" + }, + { + "ticket_id": "T-33", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 20:38:49" + }, + { + "ticket_id": "T-34", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 20:38:49" + }, + { + "ticket_id": "T-30", + "field": "status", + "old_value": "in_progress", + "new_value": "done", + "changed_at": "2026-04-22 20:38:51" + }, + { + "ticket_id": "T-31", + "field": "status", + "old_value": "in_progress", + "new_value": "done", + "changed_at": "2026-04-22 20:38:51" + }, + { + "ticket_id": "T-33", + "field": "status", + "old_value": "in_progress", + "new_value": "done", + "changed_at": "2026-04-22 20:38:51" + }, + { + "ticket_id": "T-34", + "field": "status", + "old_value": "in_progress", + "new_value": "done", + "changed_at": "2026-04-22 20:38:51" + }, + { + "ticket_id": "T-35", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 20:56:58" + }, + { + "ticket_id": "T-35", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 20:56:58" + }, + { + "ticket_id": "T-35", + "field": "status", + "old_value": "in_progress", + "new_value": "done", + "changed_at": "2026-04-22 20:56:58" + }, + { + "ticket_id": "T-32", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 21:02:29" + }, + { + "ticket_id": "T-32", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 21:02:29" + }, + { + "ticket_id": "T-32", + "field": "status", + "old_value": "in_progress", + "new_value": "done", + "changed_at": "2026-04-22 21:02:29" + }, + { + "ticket_id": "T-37", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 21:16:41" + }, + { + "ticket_id": "T-37", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 21:16:41" + }, + { + "ticket_id": "T-37", + "field": "status", + "old_value": "in_progress", + "new_value": "done", + "changed_at": "2026-04-22 21:16:41" + }, + { + "ticket_id": "T-38", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 21:16:41" + }, + { + "ticket_id": "T-38", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 21:16:41" + }, + { + "ticket_id": "T-38", + "field": "status", + "old_value": "in_progress", + "new_value": "done", + "changed_at": "2026-04-22 21:16:41" + }, + { + "ticket_id": "T-39", + "field": "status", + "old_value": "backlog", + "new_value": "ready", + "changed_at": "2026-04-22 21:16:41" + }, + { + "ticket_id": "T-39", + "field": "status", + "old_value": "ready", + "new_value": "in_progress", + "changed_at": "2026-04-22 21:16:41" + }, + { + "ticket_id": "T-39", + "field": "status", + "old_value": "in_progress", + "new_value": "done", + "changed_at": "2026-04-22 21:16:41" + } + ] +} \ No newline at end of file