From 4d515ce51e8f3754fbbcbb26689e4a70a0c00f9a Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Tue, 21 Apr 2026 17:10:42 +0200 Subject: [PATCH] scaffold decisions/ with backlog from planning sessions Adopt settled-reach's Q&D record convention. Confirmed decisions live under decisions/.md as D-NNN; open questions under questions-.md as Q-NNN; rejected alternatives in rejected.md as R-NNN. Markdown is source of truth; .pql/pql.db (added later) is a query index. Backlog captured from the Tier-0 Flutter planning sessions: bare WidgetsApp, theme pipeline, kernel admission rule, feature-first layout, a11y + i18n as Tier-0 contracts, test pyramid, kanban over Scrum, pql-owns-planning, Python stopgap sunset clause. ADR migration (D-001, D-003-D-006 confirmed, R-002 rejected) is staged for the next commit so the diff stays readable. Co-Authored-By: Claude --- CHANGELOG.md | 14 ++++ DECISIONS.md | 1 + decisions/README.md | 107 +++++++++++++++++++++++++++ decisions/accessibility.md | 28 +++++++ decisions/architecture.md | 67 +++++++++++++++++ decisions/extensions.md | 46 ++++++++++++ decisions/process.md | 57 ++++++++++++++ decisions/questions-accessibility.md | 17 +++++ decisions/questions-architecture.md | 62 ++++++++++++++++ decisions/questions-extensions.md | 25 +++++++ decisions/questions-process.md | 44 +++++++++++ decisions/questions-testing.md | 17 +++++ decisions/questions.md | 24 ++++++ decisions/rejected.md | 53 +++++++++++++ decisions/testing.md | 63 ++++++++++++++++ decisions/tooling.md | 28 +++++++ 16 files changed, 653 insertions(+) create mode 100644 DECISIONS.md create mode 100644 decisions/README.md create mode 100644 decisions/accessibility.md create mode 100644 decisions/architecture.md create mode 100644 decisions/extensions.md create mode 100644 decisions/process.md create mode 100644 decisions/questions-accessibility.md create mode 100644 decisions/questions-architecture.md create mode 100644 decisions/questions-extensions.md create mode 100644 decisions/questions-process.md create mode 100644 decisions/questions-testing.md create mode 100644 decisions/questions.md create mode 100644 decisions/rejected.md create mode 100644 decisions/testing.md create mode 100644 decisions/tooling.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 72dc12ab..e932a038 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,20 @@ heading, and (b) bumping `project.yaml` `version:` in the same commit. ## [Unreleased] +### Added + +- `decisions/` at the repo root — Q&D record system ported from + settled-reach and adapted for clide's domains. Confirmed decisions + (`D-NNN`) live under domain files (`architecture.md`, `extensions.md`, + `accessibility.md`, `testing.md`, `tooling.md`, `process.md`); open + questions (`Q-NNN`) live under parallel `questions-.md`; + rejected alternatives (`R-NNN`) live in `rejected.md`. Record shape, + claiming rules, and the eventual pql-side tooling plan are documented + in `decisions/README.md`. Migration of the existing `docs/ADRs/` into + these files lands in a follow-up commit. +- `DECISIONS.md` one-line pointer at the repo root (matches + settled-reach's convention). + ### Removed - Go sidecar skeleton under `sidecar/` — `cmd/clide/main.go`, `go.mod`, and the `internal/*` packages (`cli`, `daemon`, `diag`, `git`, `ipc`, `pql`, `proc`, `pty`, `version`). Deleted wholesale per [ADR 0005](docs/ADRs/0005-dart-core-ptyc-peer.md): the "sidecar language: Go" premise no longer holds once the core is Dart. All functionality listed for those packages will be reimplemented under `lib/` as part of Tier 0. diff --git a/DECISIONS.md b/DECISIONS.md new file mode 100644 index 00000000..95c2c43c --- /dev/null +++ b/DECISIONS.md @@ -0,0 +1 @@ +See [`decisions/README.md`](decisions/README.md). diff --git a/decisions/README.md b/decisions/README.md new file mode 100644 index 00000000..5952c0ac --- /dev/null +++ b/decisions/README.md @@ -0,0 +1,107 @@ +# Decisions + +Confirmed decisions, open questions, and rejected alternatives for clide. + +Decisions are split by domain. When unsure where a record belongs: if +it constrains **how we build**, it's architecture. If it defines **what +ships to users**, it's extensions / accessibility. If it defines **how +we verify**, it's testing. If it defines **what the toolchain looks +like**, it's tooling. If it defines **how the team works**, it's +process. + +Cross-domain records live in one file with `[D-NNN]`-shaped cross- +references in related files. Split threshold: when any file exceeds +~350 lines, review whether it should split (see settled-reach's +`questions-*.md` split pattern for precedent). + +## Domain files + +| File | Domain | +|------|--------| +| [architecture.md](architecture.md) | Core, rendering, IPC, kernel, panel manager | +| [extensions.md](extensions.md) | Extension contract, Lua runtime, grain, contribution points | +| [accessibility.md](accessibility.md) | A11y + i18n policy, WCAG gates | +| [testing.md](testing.md) | Test pyramid, drivers, client-side constraint | +| [tooling.md](tooling.md) | Toolchain, supply chain, CI, ignore strategy | +| [process.md](process.md) | Q&D system, kanban, commit conventions, changelog | +| [rejected.md](rejected.md) | Rejected alternatives across all domains | +| [questions.md](questions.md) | Master index of open questions | +| [questions-architecture.md](questions-architecture.md) | Architecture Qs | +| [questions-extensions.md](questions-extensions.md) | Extension Qs | +| [questions-accessibility.md](questions-accessibility.md) | A11y / i18n Qs | +| [questions-testing.md](questions-testing.md) | Testing Qs | +| [questions-process.md](questions-process.md) | Process + tooling Qs | + +## Record shape + +Confirmed decisions (`D-NNN`): + +```markdown +### D-NNN: Short title +- **Date:** YYYY-MM-DD +- **Decision:** one-sentence summary, then details. +- **Rationale:** why this over alternatives. +- **Cost:** known downsides / what we're accepting. +- **Raised by:** who proposed / endorsed. +``` + +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)`. + +Open questions (`Q-NNN`): + +```markdown +### Q-NNN: Short question-form title +- **Status:** Open | Partially resolved → [D-NNN] | Resolved → [D-NNN] +- **Question:** ... +- **Context:** ... +- **Assigned to:** (optional) +- **Source:** (optional) +``` + +Rejected alternatives (`R-NNN`): + +```markdown +### R-NNN: Short rejected-option title +- **Rejected:** YYYY-MM-DD +- **Reason:** ... +- **Cross-reference:** [D-NNN] (what was picked instead) +``` + +## Claiming an ID + +Until the pql planning subcommands land ([`Q-021`](questions-process.md)), +claim IDs by inspecting the highest existing `D-NNN` / `Q-NNN` / +`R-NNN` in the target file and incrementing. + +Once `pql decisions claim D "title"` exists, use that — +same semantics, no race on concurrent sessions. + +## Querying + +Stopgap today: `tools/scripts/plan decisions …` reads and writes +`.pql/pql.db` (gitignored; markdown is source of truth). Replaced +by `pql decisions …` when pql ships parity — see +[D-040](process.md#d-040-python-stopgap-under-toolsscriptsplan) and +[R-011](rejected.md#r-011-permanent-stopgap). + +Common queries: + +```bash +tools/scripts/plan decisions list --type confirmed --domain architecture +tools/scripts/plan decisions show D-005 --with-refs +tools/scripts/plan decisions coverage # D-records without tickets +tools/scripts/plan decisions validate # pre-push parser gate +``` + +## Adding a decision + +1. Edit the appropriate domain file. +2. Follow the record shape above. +3. Run `tools/scripts/plan decisions validate` (also runs in + `make push-check`). +4. Commit. The SQLite index rebuilds from markdown on any + `tools/scripts/plan decisions sync`. diff --git a/decisions/accessibility.md b/decisions/accessibility.md new file mode 100644 index 00000000..0f6c2cfd --- /dev/null +++ b/decisions/accessibility.md @@ -0,0 +1,28 @@ +# Accessibility + i18n Decisions + +A11y + i18n are Tier-0 contracts, not Tier-6 polish. + +--- + +### D-020: 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) +- **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. +- **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 +- **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. +- **Cost:** Third-party themes (Tier 6) won't be gated until an extension-time test hook lands. Bundled themes are gated today. +- **Raised by:** 2026-04-21 planning. + +--- diff --git a/decisions/architecture.md b/decisions/architecture.md new file mode 100644 index 00000000..bc271921 --- /dev/null +++ b/decisions/architecture.md @@ -0,0 +1,67 @@ +# Architecture Decisions + +Core, rendering, IPC, kernel, panel manager. + +--- + +### D-007: 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). +- **Raised by:** 2026-04-21 planning. + +### D-008: 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 +- **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. +- **Raised by:** 2026-04-21 planning. + +### D-010: 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). +- **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 +- **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 +- **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 +- **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 +- **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. +- **Cost:** Disabling `builtin.default_layout` by mistake produces an empty window. Mitigated by the kernel's first-boot defaults and a "reset extensions" action. +- **Raised by:** 2026-04-21 planning. + +--- + +*Architectural backlog from the claudian lineage lives below; ADR +migrations (D-001, D-003, D-004, D-005, D-006) follow when commit #2 +runs.* diff --git a/decisions/extensions.md b/decisions/extensions.md new file mode 100644 index 00000000..f3fdb07b --- /dev/null +++ b/decisions/extensions.md @@ -0,0 +1,46 @@ +# Extension Decisions + +Extension contract, Lua runtime, grain, contribution points. + +--- + +### D-015: 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 +- **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. +- **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 +- **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 +- **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)). +- **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 +- **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. + +--- + +*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)).* diff --git a/decisions/process.md b/decisions/process.md new file mode 100644 index 00000000..b306da52 --- /dev/null +++ b/decisions/process.md @@ -0,0 +1,57 @@ +# Process Decisions + +Q&D record system itself, kanban, commit conventions, changelog. + +--- + +### D-034: 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 +- **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). +- **Raised by:** 2026-04-21 planning. + +### D-036: `.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 +- **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 +- **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 `project.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 +- **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). +- **Raised by:** 2026-04-21 planning. + +### D-040: 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). +- **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/`. +- **Raised by:** 2026-04-21 planning. + +--- diff --git a/decisions/questions-accessibility.md b/decisions/questions-accessibility.md new file mode 100644 index 00000000..633f3d4d --- /dev/null +++ b/decisions/questions-accessibility.md @@ -0,0 +1,17 @@ +# Open Questions — Accessibility + i18n + +--- + +### Q-013: 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 +- **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? +- **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 new file mode 100644 index 00000000..15039b52 --- /dev/null +++ b/decisions/questions-architecture.md @@ -0,0 +1,62 @@ +# Open Questions — Architecture + +IPC, events, canvas, window chrome, macOS signing, pql absorption, +ticket persistence. + +--- + +### Q-001: 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)). + +### Q-002: 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)). + +### Q-003: 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)). + +### Q-004: `.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:` +- **Status:** Open +- **Question:** When do we freeze the IPC envelope / schema and introduce `schema_version:` in `project.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." +- **Source:** CLAUDE.md "Open questions" footer. + +### Q-006: Window chrome — native frame vs frameless custom +- **Status:** Open +- **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; decision deferred. +- **Source:** 2026-04-21 planning. + +### Q-007: 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 +- **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). +- **Source:** 2026-04-21 planning. + +### Q-022: 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. +- **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. + +--- diff --git a/decisions/questions-extensions.md b/decisions/questions-extensions.md new file mode 100644 index 00000000..86f47a57 --- /dev/null +++ b/decisions/questions-extensions.md @@ -0,0 +1,25 @@ +# Open Questions — Extensions + +Extension API shape, Lua runtime vendoring, manifest schema version. + +--- + +### Q-008: 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)). +- **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 +- **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)? +- **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:` +- **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. +- **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 new file mode 100644 index 00000000..86665a80 --- /dev/null +++ b/decisions/questions-process.md @@ -0,0 +1,44 @@ +# Open Questions — Process + Tooling + +Editor tab, tree-sitter, icon set, theme hot-reload, kernel DB access. + +Tooling-domain questions currently live here too. Split into +`questions-tooling.md` if this file outgrows ~350 lines. + +--- + +### Q-015: 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 +- **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. +- **Source:** 2026-04-21 planning. + +### Q-017: 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. +- **Source:** 2026-04-21 planning. + +### Q-018: 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) +- **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). + +### Q-020: 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)). +- **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 new file mode 100644 index 00000000..f664d326 --- /dev/null +++ b/decisions/questions-testing.md @@ -0,0 +1,17 @@ +# Open Questions — Testing + +--- + +### Q-011: 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) +- **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)? +- **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 new file mode 100644 index 00000000..2efecba2 --- /dev/null +++ b/decisions/questions.md @@ -0,0 +1,24 @@ +# Open Questions — Master Index + +Open questions live in per-domain `questions-.md` files. +This index is a pointer and a place to record the most-load-bearing +open questions with one-line summaries. + +## By domain + +| File | Topics | +|------|--------| +| [questions-architecture.md](questions-architecture.md) | IPC, events, canvas, window chrome, macOS signing, pql absorption, ticket persistence | +| [questions-extensions.md](questions-extensions.md) | Extension API shape, Lua runtime vendoring, manifest schema version | +| [questions-accessibility.md](questions-accessibility.md) | Web-mode a11y, i18n plurals/gender/dates | +| [questions-testing.md](questions-testing.md) | Coverage gates, screen-reader automation | +| [questions-process.md](questions-process.md) | Editor tab (LSP vs tree-sitter), icon set, theme hot-reload, kernel DB access, planning-tool location | + +## 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 `project.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. + +--- diff --git a/decisions/rejected.md b/decisions/rejected.md new file mode 100644 index 00000000..1c8027da --- /dev/null +++ b/decisions/rejected.md @@ -0,0 +1,53 @@ +# Rejected Alternatives + +Alternatives considered and rejected, with rationale preserved for +future reference. + +--- + +### R-003: `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) + +### R-004: 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) + +### R-005: 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) + +### R-006: 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) + +### R-007: `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) + +### R-008: 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) + +### R-009: 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) + +### R-010: 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) + +### R-011: 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) + +--- diff --git a/decisions/testing.md b/decisions/testing.md new file mode 100644 index 00000000..5a868ffd --- /dev/null +++ b/decisions/testing.md @@ -0,0 +1,63 @@ +# Testing Decisions + +Test pyramid, drivers, client-side constraint. + +--- + +### D-023: 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)). +- **Raised by:** 2026-04-21 planning. + +### D-024: 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 +- **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 +- **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. +- **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 +- **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/` +- **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 +- **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 +- **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. +- **Cost:** pql / daemon / extension tests stand up real subprocesses and real sockets locally — no mocked network convenience. +- **Raised by:** 2026-04-21 planning. + +--- diff --git a/decisions/tooling.md b/decisions/tooling.md new file mode 100644 index 00000000..52ca3f01 --- /dev/null +++ b/decisions/tooling.md @@ -0,0 +1,28 @@ +# Tooling Decisions + +Toolchain, supply chain, CI, ignore strategy. + +--- + +### D-031: 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 +- **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-033: 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). + +---