test / unit + widget + golden + a11y (push) Failing after 34s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m2s
Adds three widget tests under TerminalView — selection gestures: double-tap selects a word; long-press (touch) selects a word and keeps the selection across move-update; mouse drag selects characters across drag-start and drag-update. Each verifies the side-effect on the externally-supplied TerminalController. Coverage: gesture_detector.dart 42/50 -> 50/50; gesture_handler.dart 39/59 -> 55/59. The 4 remaining uncovered lines (147, 148, 151, 152) are the dead tertiary-tap state methods filed as T-95 — same shape as T-93 but on middle-click. Floor stays at 69 (69.79% measured; integer threshold unchanged). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4534 lines
222 KiB
JSON
4534 lines
222 KiB
JSON
{
|
||
"exported_at": "2026-05-08T16:16: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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-10",
|
||
"type": "confirmed",
|
||
"domain": "architecture",
|
||
"title": "State management — `ChangeNotifier` + `ListenableBuilder`",
|
||
"status": "active",
|
||
"date": "2026-04-21",
|
||
"file_path": "decisions/architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-12",
|
||
"type": "confirmed",
|
||
"domain": "architecture",
|
||
"title": "Kernel admission rule — mandatory shared singletons only",
|
||
"status": "active",
|
||
"date": "2026-04-21",
|
||
"file_path": "decisions/architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-14",
|
||
"type": "confirmed",
|
||
"domain": "architecture",
|
||
"title": "Two-tier disable — kernel locked, everything else extension-shaped",
|
||
"status": "active",
|
||
"date": "2026-04-21",
|
||
"file_path": "decisions/architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-15",
|
||
"type": "confirmed",
|
||
"domain": "extensions",
|
||
"title": "Extension grain — container-level, multi-contribution",
|
||
"status": "active",
|
||
"date": "2026-04-21",
|
||
"file_path": "decisions/extensions.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-23",
|
||
"type": "confirmed",
|
||
"domain": "testing",
|
||
"title": "Test pyramid — seven layers",
|
||
"status": "active",
|
||
"date": "2026-04-21",
|
||
"file_path": "decisions/testing.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-24",
|
||
"type": "confirmed",
|
||
"domain": "testing",
|
||
"title": "Golden tests — primitives only, Alchemist + Ahem",
|
||
"status": "active",
|
||
"date": "2026-04-21",
|
||
"file_path": "decisions/testing.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-25",
|
||
"type": "confirmed",
|
||
"domain": "testing",
|
||
"title": "Mocks — mocktail at IO, hand-rolled fakes for ChangeNotifiers",
|
||
"status": "active",
|
||
"date": "2026-04-21",
|
||
"file_path": "decisions/testing.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-26",
|
||
"type": "confirmed",
|
||
"domain": "testing",
|
||
"title": "Web driver — raw Playwright + Flutter semantics",
|
||
"status": "active",
|
||
"date": "2026-04-21",
|
||
"file_path": "decisions/testing.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-28",
|
||
"type": "confirmed",
|
||
"domain": "testing",
|
||
"title": "Test organisation — mirror `lib/` in `test/`",
|
||
"status": "active",
|
||
"date": "2026-04-21",
|
||
"file_path": "decisions/testing.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-29",
|
||
"type": "confirmed",
|
||
"domain": "testing",
|
||
"title": "Pre-push gate — fast layer only",
|
||
"status": "active",
|
||
"date": "2026-04-21",
|
||
"file_path": "decisions/testing.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-32",
|
||
"type": "confirmed",
|
||
"domain": "tooling",
|
||
"title": "CI — Gitea primary, Linux-only runners, not yet activated",
|
||
"status": "active",
|
||
"date": "2026-04-21",
|
||
"file_path": "decisions/tooling.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-33",
|
||
"type": "confirmed",
|
||
"domain": "tooling",
|
||
"title": "Golden-output ignore pattern — `coverage.*` excludes output, not scripts",
|
||
"status": "active",
|
||
"date": "2026-04-21",
|
||
"file_path": "decisions/tooling.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-34",
|
||
"type": "confirmed",
|
||
"domain": "process",
|
||
"title": "Q\u0026D record system",
|
||
"status": "active",
|
||
"date": "2026-04-21",
|
||
"file_path": "decisions/process.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-38",
|
||
"type": "confirmed",
|
||
"domain": "process",
|
||
"title": "Changelog discipline — Keep a Changelog 1.1.0",
|
||
"status": "active",
|
||
"date": "2026-04-21",
|
||
"file_path": "decisions/process.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-41",
|
||
"type": "confirmed",
|
||
"domain": "architecture",
|
||
"title": "Claude panes — one primary per repo, tmux-backed",
|
||
"status": "active",
|
||
"date": "2026-04-22",
|
||
"file_path": "decisions/architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-43",
|
||
"type": "confirmed",
|
||
"domain": "architecture",
|
||
"title": "Design handoff — adopt token palettes, reject Material wrapper",
|
||
"status": "active",
|
||
"date": "2026-04-22",
|
||
"file_path": "decisions/architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-44",
|
||
"type": "confirmed",
|
||
"domain": "architecture",
|
||
"title": "Four bundled themes — clide, midnight, paper, terminal",
|
||
"status": "active",
|
||
"date": "2026-04-22",
|
||
"file_path": "decisions/architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-47",
|
||
"type": "confirmed",
|
||
"domain": "architecture",
|
||
"title": "Interaction model — Claude-is-home layout",
|
||
"status": "active",
|
||
"date": "2026-04-22",
|
||
"file_path": "decisions/architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-48",
|
||
"type": "confirmed",
|
||
"domain": "architecture",
|
||
"title": "Chrome budget — no tabs, no breadcrumbs, keyboard-first",
|
||
"status": "active",
|
||
"date": "2026-04-22",
|
||
"file_path": "decisions/architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-49",
|
||
"type": "confirmed",
|
||
"domain": "architecture",
|
||
"title": "Editor mode — inline above Claude, viewer swap",
|
||
"status": "active",
|
||
"date": "2026-04-22",
|
||
"file_path": "decisions/architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-50",
|
||
"type": "confirmed",
|
||
"domain": "architecture",
|
||
"title": "Context auto-behavior — right panel reacts to Claude",
|
||
"status": "active",
|
||
"date": "2026-04-22",
|
||
"file_path": "decisions/architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-51",
|
||
"type": "confirmed",
|
||
"domain": "architecture",
|
||
"title": "Panel collapse — 12px spine with badge",
|
||
"status": "active",
|
||
"date": "2026-04-22",
|
||
"file_path": "decisions/architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-52",
|
||
"type": "confirmed",
|
||
"domain": "architecture",
|
||
"title": "Focus mode — full-window takeover",
|
||
"status": "active",
|
||
"date": "2026-04-22",
|
||
"file_path": "decisions/architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-54",
|
||
"type": "confirmed",
|
||
"domain": "architecture",
|
||
"title": "Keyboard map — canonical shortcuts",
|
||
"status": "active",
|
||
"date": "2026-04-22",
|
||
"file_path": "decisions/architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-59",
|
||
"type": "confirmed",
|
||
"domain": "tooling",
|
||
"title": "Bundled git via dugite-native",
|
||
"status": "active",
|
||
"date": "2026-04-25",
|
||
"file_path": "decisions/tooling.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-60",
|
||
"type": "confirmed",
|
||
"domain": "tooling",
|
||
"title": "No network on default launch path",
|
||
"status": "active",
|
||
"date": "2026-04-26",
|
||
"file_path": "decisions/tooling.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-61",
|
||
"type": "confirmed",
|
||
"domain": "tooling",
|
||
"title": "Dependency vetting checklist",
|
||
"status": "active",
|
||
"date": "2026-04-26",
|
||
"file_path": "decisions/tooling.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-62",
|
||
"type": "confirmed",
|
||
"domain": "tooling",
|
||
"title": "Dependency removal process",
|
||
"status": "active",
|
||
"date": "2026-04-26",
|
||
"file_path": "decisions/tooling.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-63",
|
||
"type": "confirmed",
|
||
"domain": "tooling",
|
||
"title": "Vendored binary rebuild process",
|
||
"status": "active",
|
||
"date": "2026-04-26",
|
||
"file_path": "decisions/tooling.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-64",
|
||
"type": "confirmed",
|
||
"domain": "architecture",
|
||
"title": "No telemetry — architectural commitment",
|
||
"status": "active",
|
||
"date": "2026-05-03",
|
||
"file_path": "decisions/architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-65",
|
||
"type": "confirmed",
|
||
"domain": "tooling",
|
||
"title": "License compatibility matrix",
|
||
"status": "active",
|
||
"date": "2026-04-26",
|
||
"file_path": "decisions/tooling.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "D-66",
|
||
"type": "confirmed",
|
||
"domain": "testing",
|
||
"title": "Line coverage gate at 95%, ratcheted from current",
|
||
"status": "active",
|
||
"date": "2026-05-06",
|
||
"file_path": "decisions/testing.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "Q-2",
|
||
"type": "question",
|
||
"domain": "architecture",
|
||
"title": "Back-pressure on event streams",
|
||
"status": "open",
|
||
"file_path": "decisions/questions-architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "Q-3",
|
||
"type": "question",
|
||
"domain": "architecture",
|
||
"title": "Event persistence + audit/undo",
|
||
"status": "open",
|
||
"file_path": "decisions/questions-architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "Q-4",
|
||
"type": "question",
|
||
"domain": "architecture",
|
||
"title": "`.canvas` schema compatibility with Obsidian",
|
||
"status": "open",
|
||
"file_path": "decisions/questions-architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "Q-6",
|
||
"type": "question",
|
||
"domain": "architecture",
|
||
"title": "Window chrome — native frame vs frameless custom",
|
||
"status": "resolved",
|
||
"file_path": "decisions/questions-architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "Q-7",
|
||
"type": "question",
|
||
"domain": "architecture",
|
||
"title": "macOS app bundle signing / notarisation",
|
||
"status": "open",
|
||
"file_path": "decisions/questions-architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "Q-8",
|
||
"type": "question",
|
||
"domain": "extensions",
|
||
"title": "Extension API shape — widgets, subcommands, both?",
|
||
"status": "open",
|
||
"file_path": "decisions/questions-extensions.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "Q-9",
|
||
"type": "question",
|
||
"domain": "extensions",
|
||
"title": "Lua runtime vendoring",
|
||
"status": "open",
|
||
"file_path": "decisions/questions-extensions.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "Q-10",
|
||
"type": "question",
|
||
"domain": "extensions",
|
||
"title": "Extension manifest `schema_version:`",
|
||
"status": "open",
|
||
"file_path": "decisions/questions-extensions.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "Q-11",
|
||
"type": "question",
|
||
"domain": "testing",
|
||
"title": "Coverage gates — hard thresholds vs soft reporting",
|
||
"status": "open",
|
||
"file_path": "decisions/questions-testing.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "Q-13",
|
||
"type": "question",
|
||
"domain": "accessibility",
|
||
"title": "Web production-mode a11y",
|
||
"status": "open",
|
||
"file_path": "decisions/questions-accessibility.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "Q-15",
|
||
"type": "question",
|
||
"domain": "process",
|
||
"title": "Editor tab — full LSP vs tree-sitter-only highlight",
|
||
"status": "open",
|
||
"file_path": "decisions/questions-process.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "Q-16",
|
||
"type": "question",
|
||
"domain": "process",
|
||
"title": "`tree-sitter-dart` grammar maintenance",
|
||
"status": "open",
|
||
"file_path": "decisions/questions-process.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "Q-17",
|
||
"type": "question",
|
||
"domain": "process",
|
||
"title": "Icon set growth",
|
||
"status": "open",
|
||
"file_path": "decisions/questions-process.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "Q-19",
|
||
"type": "question",
|
||
"domain": "process",
|
||
"title": "(withdrawn)",
|
||
"status": "resolved",
|
||
"file_path": "decisions/questions-process.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "Q-20",
|
||
"type": "question",
|
||
"domain": "process",
|
||
"title": "Kernel DB service — namespaced SQL access?",
|
||
"status": "open",
|
||
"file_path": "decisions/questions-process.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "Q-22",
|
||
"type": "question",
|
||
"domain": "architecture",
|
||
"title": "Ticket persistence strategy",
|
||
"status": "open",
|
||
"file_path": "decisions/questions-architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "Q-23",
|
||
"type": "question",
|
||
"domain": "architecture",
|
||
"title": "SSH-remote development — run clide against a remote workspace",
|
||
"status": "open",
|
||
"file_path": "decisions/questions-architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "Q-25",
|
||
"type": "question",
|
||
"domain": "architecture",
|
||
"title": "Body text face — mono everywhere vs Josefin Sans UI + mono code",
|
||
"status": "open",
|
||
"file_path": "decisions/questions-architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "Q-26",
|
||
"type": "question",
|
||
"domain": "architecture",
|
||
"title": "Small screen layout (\u003c 1000px)",
|
||
"status": "open",
|
||
"file_path": "decisions/questions-architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "Q-27",
|
||
"type": "question",
|
||
"domain": "architecture",
|
||
"title": "Two-editor split",
|
||
"status": "open",
|
||
"file_path": "decisions/questions-architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "Q-28",
|
||
"type": "question",
|
||
"domain": "architecture",
|
||
"title": "Terminal strip scope — shell only or logs/errors/tests",
|
||
"status": "open",
|
||
"file_path": "decisions/questions-architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "Q-29",
|
||
"type": "question",
|
||
"domain": "architecture",
|
||
"title": "Branch picker location",
|
||
"status": "open",
|
||
"file_path": "decisions/questions-architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "Q-31",
|
||
"type": "question",
|
||
"domain": "architecture",
|
||
"title": "XWayland fallback for frameless — proper Wayland protocol needed",
|
||
"status": "open",
|
||
"file_path": "decisions/questions-architecture.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "R-2",
|
||
"type": "rejected",
|
||
"domain": "rejected",
|
||
"title": "Go sidecar",
|
||
"status": "active",
|
||
"date": "2026-04-20",
|
||
"file_path": "decisions/rejected.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "R-3",
|
||
"type": "rejected",
|
||
"domain": "rejected",
|
||
"title": "`MaterialApp` root",
|
||
"status": "active",
|
||
"date": "2026-04-21",
|
||
"file_path": "decisions/rejected.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "R-7",
|
||
"type": "rejected",
|
||
"domain": "rejected",
|
||
"title": "`CupertinoApp` root",
|
||
"status": "active",
|
||
"date": "2026-04-21",
|
||
"file_path": "decisions/rejected.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
},
|
||
{
|
||
"id": "R-11",
|
||
"type": "rejected",
|
||
"domain": "rejected",
|
||
"title": "Permanent stopgap",
|
||
"status": "active",
|
||
"date": "2026-04-21",
|
||
"file_path": "decisions/rejected.md",
|
||
"synced_at": "2026-05-08 11:03:39"
|
||
},
|
||
{
|
||
"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-05-08 11:03:39"
|
||
}
|
||
],
|
||
"decision_refs": [
|
||
{
|
||
"source_id": "D-1",
|
||
"target_id": "D-6",
|
||
"ref_type": "references",
|
||
"note": "**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 ..."
|
||
},
|
||
{
|
||
"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-25-mocks-mocktail-at-io-plus-hand-rolled-fakes)). Violates [D-31 prefer-zero-deps](tooling...."
|
||
},
|
||
{
|
||
"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-25-mocks-mocktail-at-io-plus-hand-rolled-fakes)). Violates [D-31 prefer-zero-deps](tooling...."
|
||
},
|
||
{
|
||
"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-25-mocks-mocktail-at-io-plus-hand-rolled-fakes)). Violates [D-31 prefer-zero-deps](tooling...."
|
||
},
|
||
{
|
||
"source_id": "D-16",
|
||
"target_id": "D-19",
|
||
"ref_type": "references",
|
||
"note": "**Decision:** Bundled extensions (every `lib/builtin/\u003cname\u003e`) are Dart — they link into the app binary. Third-party extensions (Tier 6) run in sandboxed Lua via the `ptyc`-peer Lua runtime (see [..."
|
||
},
|
||
{
|
||
"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 — see [D-21](accessibility.md#d-21-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-4-flutter-intl-and-arb-codegen)) — we need per-extension catalogs that merge w..."
|
||
},
|
||
{
|
||
"source_id": "D-23",
|
||
"target_id": "D-29",
|
||
"ref_type": "references",
|
||
"note": "**Cost:** Seven CI jobs; total wall time budgeted at \u003c 15 min. Pre-push runs layers 1-4 (\u003c 90 s — see [D-29](#d-29-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-20-a11y-is-..."
|
||
},
|
||
{
|
||
"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-39-planning-tooling-lives-in-pql)."
|
||
},
|
||
{
|
||
"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-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-22-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 p..."
|
||
},
|
||
{
|
||
"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-40-superseded-python-stopgap-under-toolsscriptsplan). Gated by [Q-21](questions-process.md#q-21-pql-absorbs-plann..."
|
||
},
|
||
{
|
||
"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-40-superseded-python-stopgap-under-toolsscriptsplan). Gated by [Q-21](questions-process.md#q-21-pql-absorbs-plann..."
|
||
},
|
||
{
|
||
"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\u0026D access. Binding planning tooling to clide-the-Flutter-app would cut them off from their own work — see [R-9](reject..."
|
||
},
|
||
{
|
||
"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-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-10-python-script-stopgap-at-toolingdb) for why `too..."
|
||
},
|
||
{
|
||
"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-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), [`..."
|
||
},
|
||
{
|
||
"source_id": "D-41",
|
||
"target_id": "D-6",
|
||
"ref_type": "references",
|
||
"note": "**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), [`..."
|
||
},
|
||
{
|
||
"source_id": "D-41",
|
||
"target_id": "R-9",
|
||
"ref_type": "references",
|
||
"note": "**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), [`..."
|
||
},
|
||
{
|
||
"source_id": "D-42",
|
||
"target_id": "D-31",
|
||
"ref_type": "references",
|
||
"note": "**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 ..."
|
||
},
|
||
{
|
||
"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-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)."
|
||
},
|
||
{
|
||
"source_id": "D-43",
|
||
"target_id": "R-12",
|
||
"ref_type": "references",
|
||
"note": "**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)."
|
||
},
|
||
{
|
||
"source_id": "D-44",
|
||
"target_id": "D-22",
|
||
"ref_type": "references",
|
||
"note": "**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)."
|
||
},
|
||
{
|
||
"source_id": "D-44",
|
||
"target_id": "D-43",
|
||
"ref_type": "references",
|
||
"note": "**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)."
|
||
},
|
||
{
|
||
"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 — 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-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-inlin..."
|
||
},
|
||
{
|
||
"source_id": "D-47",
|
||
"target_id": "D-49",
|
||
"ref_type": "references",
|
||
"note": "**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-cl..."
|
||
},
|
||
{
|
||
"source_id": "D-48",
|
||
"target_id": "D-47",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-47](#d-47-interaction-model-claude-is-home-layout), [D-49](#d-49-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 — 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 open..."
|
||
},
|
||
{
|
||
"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-48-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first)). Power users wanting two files side-by-side must wait for split (see ..."
|
||
},
|
||
{
|
||
"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-48-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first)). Power users wanting two files side-by-side must wait for split (see ..."
|
||
},
|
||
{
|
||
"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-56-dissolve-daemon-process-flutter-app-hosts-ipc-server). Dart-core principle survives; th..."
|
||
},
|
||
{
|
||
"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-2-go-sidecar))"
|
||
},
|
||
{
|
||
"source_id": "D-50",
|
||
"target_id": "D-47",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-47](#d-47-interaction-model-claude-is-home-layout), [D-51](#d-51-panel-collapse-12px-spine-with-badge)."
|
||
},
|
||
{
|
||
"source_id": "D-50",
|
||
"target_id": "D-51",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-47](#d-47-interaction-model-claude-is-home-layout), [D-51](#d-51-panel-collapse-12px-spine-with-badge)."
|
||
},
|
||
{
|
||
"source_id": "D-51",
|
||
"target_id": "D-47",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-47](#d-47-interaction-model-claude-is-home-layout), [D-50](#d-50-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-47-interaction-model-claude-is-home-layout), [D-50](#d-50-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-53-state-persistence-a..."
|
||
},
|
||
{
|
||
"source_id": "D-52",
|
||
"target_id": "D-47",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-47](#d-47-interaction-model-claude-is-home-layout), [D-53](#d-53-state-persistence-across-sessions)."
|
||
},
|
||
{
|
||
"source_id": "D-52",
|
||
"target_id": "D-53",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-47](#d-47-interaction-model-claude-is-home-layout), [D-53](#d-53-state-persistence-across-sessions)."
|
||
},
|
||
{
|
||
"source_id": "D-53",
|
||
"target_id": "D-47",
|
||
"ref_type": "references",
|
||
"note": "**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)."
|
||
},
|
||
{
|
||
"source_id": "D-53",
|
||
"target_id": "D-51",
|
||
"ref_type": "references",
|
||
"note": "**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)."
|
||
},
|
||
{
|
||
"source_id": "D-53",
|
||
"target_id": "D-52",
|
||
"ref_type": "references",
|
||
"note": "**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)."
|
||
},
|
||
{
|
||
"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-17-panels-are-extension-shaped-from-day-one)) allows user ov..."
|
||
},
|
||
{
|
||
"source_id": "D-54",
|
||
"target_id": "D-47",
|
||
"ref_type": "references",
|
||
"note": "**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)."
|
||
},
|
||
{
|
||
"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-48-chrome-budget-no-tabs-no-breadcrumbs-keyboard-first). The set is minimal and covers all layou..."
|
||
},
|
||
{
|
||
"source_id": "D-54",
|
||
"target_id": "D-52",
|
||
"ref_type": "references",
|
||
"note": "**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)."
|
||
},
|
||
{
|
||
"source_id": "D-54",
|
||
"target_id": "Q-26",
|
||
"ref_type": "references",
|
||
"note": "**Decision:** Canonical keyboard shortcuts (cross-platform, `⌘` = `Ctrl` on Linux): `⌘P` fuzzy find overlay; `⌘⇧1` / `⌘⇧3` collapse/expand left / right panel; `⌘1` / `⌘2` / `⌘3` 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 — not as workspace-level tabs (which would violate [D-48](#d-48-chrome-budget-no-tabs-no-..."
|
||
},
|
||
{
|
||
"source_id": "D-55",
|
||
"target_id": "D-47",
|
||
"ref_type": "references",
|
||
"note": "**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 th..."
|
||
},
|
||
{
|
||
"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 — not as workspace-level tabs (which would violate [D-48](#d-48-chrome-budget-no-tabs-no-..."
|
||
},
|
||
{
|
||
"source_id": "D-56",
|
||
"target_id": "D-1",
|
||
"ref_type": "references",
|
||
"note": "**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..."
|
||
},
|
||
{
|
||
"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-56",
|
||
"target_id": "D-5",
|
||
"ref_type": "references",
|
||
"note": "**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 se..."
|
||
},
|
||
{
|
||
"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 — 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-47-interaction-model-claude-is-home-layout) (center hat always visible), [D-51](#d-51-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-47-interaction-model-claude-is-home-layout) (center hat always visible), [D-51](#d-51-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-6-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 — every new dependency needs justification. This record clarifies that format engines clear the justification bar..."
|
||
},
|
||
{
|
||
"source_id": "D-58",
|
||
"target_id": "D-42",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-31](#d-31-prefer-zero-deps-exact-pin), [D-42](#d-42-dependencies-documented-in-licensesyaml)."
|
||
},
|
||
{
|
||
"source_id": "D-59",
|
||
"target_id": "D-31",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-31](#d-31-prefer-zero-deps-exact-pin), [D-42](#d-42-dependencies-documented-in-licensesyaml)."
|
||
},
|
||
{
|
||
"source_id": "D-59",
|
||
"target_id": "D-42",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-31](#d-31-prefer-zero-deps-exact-pin), [D-42](#d-42-dependencies-documented-in-licensesyaml)."
|
||
},
|
||
{
|
||
"source_id": "D-6",
|
||
"target_id": "D-1",
|
||
"ref_type": "references",
|
||
"note": "**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 colloquiall..."
|
||
},
|
||
{
|
||
"source_id": "D-6",
|
||
"target_id": "Q-1",
|
||
"ref_type": "references",
|
||
"note": "**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 gra..."
|
||
},
|
||
{
|
||
"source_id": "D-6",
|
||
"target_id": "Q-2",
|
||
"ref_type": "references",
|
||
"note": "**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 gra..."
|
||
},
|
||
{
|
||
"source_id": "D-6",
|
||
"target_id": "Q-3",
|
||
"ref_type": "references",
|
||
"note": "**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 gra..."
|
||
},
|
||
{
|
||
"source_id": "D-6",
|
||
"target_id": "Q-5",
|
||
"ref_type": "references",
|
||
"note": "**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 an..."
|
||
},
|
||
{
|
||
"source_id": "D-60",
|
||
"target_id": "D-31",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-31](#d-31-prefer-zero-deps-exact-pin), [D-63](#d-63-vendored-binary-rebuild-process), `POLICY.md`."
|
||
},
|
||
{
|
||
"source_id": "D-60",
|
||
"target_id": "D-63",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-31](#d-31-prefer-zero-deps-exact-pin), [D-63](#d-63-vendored-binary-rebuild-process), `POLICY.md`."
|
||
},
|
||
{
|
||
"source_id": "D-61",
|
||
"target_id": "D-31",
|
||
"ref_type": "references",
|
||
"note": "**Decision:** Before adding any dependency (direct or transitive), verify: (1) **Network behavior** — no network I/O during import, init, or first call; no postinstall scripts that download binar..."
|
||
},
|
||
{
|
||
"source_id": "D-61",
|
||
"target_id": "D-60",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-31](#d-31-prefer-zero-deps-exact-pin), [D-60](#d-60-no-network-on-default-launch-path), [D-65](#d-65-license-compatibility-matrix), `POLICY.md`."
|
||
},
|
||
{
|
||
"source_id": "D-61",
|
||
"target_id": "D-65",
|
||
"ref_type": "references",
|
||
"note": "**Decision:** Before adding any dependency (direct or transitive), verify: (1) **Network behavior** — no network I/O during import, init, or first call; no postinstall scripts that download binar..."
|
||
},
|
||
{
|
||
"source_id": "D-62",
|
||
"target_id": "D-31",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-31](#d-31-prefer-zero-deps-exact-pin), [D-42](#d-42-dependencies-documented-in-licensesyaml), `POLICY.md`."
|
||
},
|
||
{
|
||
"source_id": "D-62",
|
||
"target_id": "D-42",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-31](#d-31-prefer-zero-deps-exact-pin), [D-42](#d-42-dependencies-documented-in-licensesyaml), `POLICY.md`."
|
||
},
|
||
{
|
||
"source_id": "D-63",
|
||
"target_id": "D-42",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-60](#d-60-no-network-on-default-launch-path), [D-42](#d-42-dependencies-documented-in-licensesyaml), T-25, `POLICY.md`."
|
||
},
|
||
{
|
||
"source_id": "D-63",
|
||
"target_id": "D-60",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-60](#d-60-no-network-on-default-launch-path), [D-42](#d-42-dependencies-documented-in-licensesyaml), T-25, `POLICY.md`."
|
||
},
|
||
{
|
||
"source_id": "D-64",
|
||
"target_id": "D-60",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-60](tooling.md#d-60-no-network-on-default-launch-path), `POLICY.md`."
|
||
},
|
||
{
|
||
"source_id": "D-65",
|
||
"target_id": "D-31",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-31](#d-31-prefer-zero-deps-exact-pin), [D-42](#d-42-dependencies-documented-in-licensesyaml), [D-61](#d-61-dependency-vetting-checklist), `POLICY.md`."
|
||
},
|
||
{
|
||
"source_id": "D-65",
|
||
"target_id": "D-42",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-31](#d-31-prefer-zero-deps-exact-pin), [D-42](#d-42-dependencies-documented-in-licensesyaml), [D-61](#d-61-dependency-vetting-checklist), `POLICY.md`."
|
||
},
|
||
{
|
||
"source_id": "D-65",
|
||
"target_id": "D-61",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-31](#d-31-prefer-zero-deps-exact-pin), [D-42](#d-42-dependencies-documented-in-licensesyaml), [D-61](#d-61-dependency-vetting-checklist), `POLICY.md`."
|
||
},
|
||
{
|
||
"source_id": "D-66",
|
||
"target_id": "D-29",
|
||
"ref_type": "references",
|
||
"note": "**Cost:** Pre-push wall time grows by `flutter test --coverage` (currently ≈11 s on this tree). Acceptable within D-29's \u003c 90 s budget; reassess if it slips. Floor bumps require an explicit edit ..."
|
||
},
|
||
{
|
||
"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-9-three-tier..."
|
||
},
|
||
{
|
||
"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-3-materialapp-root) and [R-7](rejected.md#r-7-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-3-materialapp-root) and [R-7](rejected.md#r-7-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-22-wcag-aa-contrast-gate-on-bundled-themes)) enforces the semantic layer on every bundled theme."
|
||
},
|
||
{
|
||
"source_id": "Q-1",
|
||
"target_id": "D-6",
|
||
"ref_type": "references",
|
||
"note": "**Source:** ADR 0006 (migrated to [D-6](architecture.md))."
|
||
},
|
||
{
|
||
"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-5-ipc-wire-format-stability) (IPC wire format) — both want a versioning story."
|
||
},
|
||
{
|
||
"source_id": "Q-12",
|
||
"target_id": "D-22",
|
||
"ref_type": "references",
|
||
"note": "**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 issue..."
|
||
},
|
||
{
|
||
"source_id": "Q-14",
|
||
"target_id": "D-31",
|
||
"ref_type": "references",
|
||
"note": "**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 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-15-editor-tab-full-lsp-vs-tree-sitter-only-highlight)), 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-6-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\" — no longer a split question. Markdown mirror is tracked in [Q-22](questions-architecture.md#q-22-ticket-persistence-strategy); SQL..."
|
||
},
|
||
{
|
||
"source_id": "Q-19",
|
||
"target_id": "Q-22",
|
||
"ref_type": "references",
|
||
"note": "**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); SQL..."
|
||
},
|
||
{
|
||
"source_id": "Q-2",
|
||
"target_id": "D-6",
|
||
"ref_type": "references",
|
||
"note": "**Source:** ADR 0006 (migrated to [D-6](architecture.md))."
|
||
},
|
||
{
|
||
"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_\u003cid\u003e_…`) or stay on the `kernel.settings` key/value facade? Admission-level question ([D-12](archi..."
|
||
},
|
||
{
|
||
"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 — `pql decisions …` + `pql ticket …` subcommands; clide shells out. (B) Clide absorbs pql — 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-40-python-stopgap-under-toolsscriptsplan)'s sunset condition changes. Gates all ..."
|
||
},
|
||
{
|
||
"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-40-python-stopgap-under-toolsscriptsplan)'s sunset condition changes. Gates all ..."
|
||
},
|
||
{
|
||
"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-40-python-stopgap-under-toolsscriptsplan)'s sunset condition changes. Gates all ..."
|
||
},
|
||
{
|
||
"source_id": "Q-22",
|
||
"target_id": "Q-21",
|
||
"ref_type": "references",
|
||
"note": "**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)...."
|
||
},
|
||
{
|
||
"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 — terminal + Claude panes land local-first — but the daemon/IPC seam decisions (notably `D-5` and `D-6`) constrain ..."
|
||
},
|
||
{
|
||
"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 — terminal + Claude panes land local-first — but the daemon/IPC seam decisions (notably `D-5` and `D-6`) constrain ..."
|
||
},
|
||
{
|
||
"source_id": "Q-27",
|
||
"target_id": "D-48",
|
||
"ref_type": "references",
|
||
"note": "**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 usi..."
|
||
},
|
||
{
|
||
"source_id": "Q-3",
|
||
"target_id": "D-6",
|
||
"ref_type": "references",
|
||
"note": "**Source:** ADR 0006 (migrated to [D-6](architecture.md))."
|
||
},
|
||
{
|
||
"source_id": "Q-30",
|
||
"target_id": "D-47",
|
||
"ref_type": "references",
|
||
"note": "**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) (edit..."
|
||
},
|
||
{
|
||
"source_id": "Q-30",
|
||
"target_id": "D-49",
|
||
"ref_type": "references",
|
||
"note": "**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) (edit..."
|
||
},
|
||
{
|
||
"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": "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 → [D-57](architecture.md#d-57-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-19-lua-runtime-as-ptyc-peer-supporter-tool)) bundle liblua source (build with the binary) or link system liblua (smaller binary, fr..."
|
||
},
|
||
{
|
||
"source_id": "R-10",
|
||
"target_id": "D-40",
|
||
"ref_type": "references",
|
||
"note": "**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. ..."
|
||
},
|
||
{
|
||
"source_id": "R-11",
|
||
"target_id": "D-40",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-40](process.md#d-40-python-stopgap-under-toolsscriptsplan)"
|
||
},
|
||
{
|
||
"source_id": "R-12",
|
||
"target_id": "D-43",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-43](architecture.md#d-43-design-handoff-adopt-token-palettes-reject-material-wrapper)"
|
||
},
|
||
{
|
||
"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-2",
|
||
"target_id": "D-5",
|
||
"ref_type": "references",
|
||
"note": "**Rejected:** 2026-04-20 (was ADR 0002; superseded by [D-5](architecture.md#d-5-dart-core-ptyc-peer))"
|
||
},
|
||
{
|
||
"source_id": "R-3",
|
||
"target_id": "D-7",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-7](architecture.md#d-7-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-9-three-tier-theme-pipeline)). Every bundled th..."
|
||
},
|
||
{
|
||
"source_id": "R-4",
|
||
"target_id": "D-21",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-21](accessibility.md#d-21-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-26-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-31-prefer-zero-deps-exact-pin)) for a capability we get from Playwright + Flutter's own semantics tree. Patrol's value proposition (nativ..."
|
||
},
|
||
{
|
||
"source_id": "R-6",
|
||
"target_id": "Q-17",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [Q-17](questions-process.md#q-17-icon-set-growth)"
|
||
},
|
||
{
|
||
"source_id": "R-7",
|
||
"target_id": "D-7",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-7](architecture.md#d-7-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-3-materialapp-root)."
|
||
},
|
||
{
|
||
"source_id": "R-8",
|
||
"target_id": "D-10",
|
||
"ref_type": "references",
|
||
"note": "**Cross-reference:** [D-10](architecture.md#d-10-state-management-changenotifier)"
|
||
},
|
||
{
|
||
"source_id": "R-8",
|
||
"target_id": "D-31",
|
||
"ref_type": "references",
|
||
"note": "**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 win..."
|
||
},
|
||
{
|
||
"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..."
|
||
}
|
||
],
|
||
"tickets": [
|
||
{
|
||
"id": "T-1",
|
||
"type": "task",
|
||
"parent_id": "T-6",
|
||
"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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-2",
|
||
"type": "epic",
|
||
"title": "Tier 0 — 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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-3",
|
||
"type": "epic",
|
||
"title": "Tier 1 — 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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-4",
|
||
"type": "epic",
|
||
"title": "Tier 2 — 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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-5",
|
||
"type": "epic",
|
||
"title": "Tier 3 — 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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-6",
|
||
"type": "epic",
|
||
"title": "Tier 4 — 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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-7",
|
||
"type": "epic",
|
||
"title": "Tier 5 — canvas and graph view",
|
||
"description": "Tier 5 of the build plan: bring the canvas pane (Obsidian-style spatial whiteboard) and the graph view (force-directed view of vault links) to feature parity.\n\n**Canvas pane (`builtin.canvas`):**\n- Render `.canvas` files (Obsidian-compatible JSON) — nodes (note, text, group, image), edges, layout state.\n- Pan/zoom via `InteractiveViewer`; node selection, drag, resize.\n- Edit affordances: add note from file picker, add text node, draw edge between nodes.\n- Persist layout back to the `.canvas` file on disk.\n\n**Graph view (`builtin.graph`):**\n- Force-directed layout of the current vault (or a filtered subset) — nodes are notes, edges are wikilinks.\n- Hover highlights connected subgraph; click opens the note in the editor.\n- Filter pane: tag include/exclude, file glob, depth-from-active.\n- pql provides the link data (`pql backlinks` / `pql outlinks`); rendering owned in-app.\n\nBoth panes use the existing `MultitabPane` (T-83) for tab management once they ship. Each lives in its own slot per D-47 (canvas in workspace, graph in context panel).",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"decision_ref": "D-17",
|
||
"created_at": "2026-04-22 11:45:41",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-8",
|
||
"type": "epic",
|
||
"title": "Tier 6 — extension API, settings, theming, distributable builds",
|
||
"description": "Tier 6 of the build plan: the things that make clide a real product instead of a working prototype.\n\n**Extension API (third-party Lua):**\n- The Lua runtime supporter tool (D-19) lands as a peer of pql/ptyc.\n- Manifest schema, capability gating, sandboxed FS/IPC access.\n- Same TabContribution / CommandContribution / etc. surface as built-in Dart extensions (D-15).\n- Marketplace / distribution story is OUT OF SCOPE for Tier 6 — local-install only.\n\n**Settings UI (`builtin.settings-ui`):**\n- Schema-driven settings panel reading from the kernel SettingsStore.\n- Render strategy: form fields keyed off the schema each subsystem registers.\n- Edits write back to `.clide/settings.yaml`.\n\n**Theming UI (`builtin.theme-picker` extends):**\n- Live preview of the four bundled themes (D-44).\n- Custom theme: import YAML, validate against schema, register at runtime.\n- Per-component override surface (long horizon).\n\n**Distributable builds:**\n- AppImage / Flatpak for Linux, .dmg for macOS — see T-46.\n- Self-update mechanism — see T-47.\n- License manifest auto-regen as part of the release build.\n\nBig epic — children land incrementally. Most concrete child tickets already exist; this is the umbrella.",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"decision_ref": "D-15",
|
||
"created_at": "2026-04-22 11:45:41",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-9",
|
||
"type": "task",
|
||
"parent_id": "T-2",
|
||
"title": "Q\u0026D record system + ADR migration",
|
||
"status": "done",
|
||
"priority": "medium",
|
||
"decision_ref": "D-34",
|
||
"created_at": "2026-04-22 11:47:02",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-10",
|
||
"type": "task",
|
||
"parent_id": "T-2",
|
||
"title": "Python stopgap tooling",
|
||
"status": "done",
|
||
"priority": "medium",
|
||
"decision_ref": "D-40",
|
||
"created_at": "2026-04-22 11:47:02",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-11",
|
||
"type": "task",
|
||
"parent_id": "T-6",
|
||
"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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-12",
|
||
"type": "task",
|
||
"parent_id": "T-2",
|
||
"title": "bundled dependency licensing",
|
||
"status": "done",
|
||
"priority": "medium",
|
||
"decision_ref": "D-42",
|
||
"created_at": "2026-04-22 11:47:03",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-13",
|
||
"type": "task",
|
||
"parent_id": "T-6",
|
||
"title": "pql query panel — sidebar tab for vault queries",
|
||
"status": "done",
|
||
"priority": "medium",
|
||
"decision_ref": "D-3",
|
||
"created_at": "2026-04-22 11:47:18",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-14",
|
||
"type": "task",
|
||
"parent_id": "T-6",
|
||
"title": "pql-backed file tree — 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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-15",
|
||
"type": "task",
|
||
"parent_id": "T-6",
|
||
"title": "backlinks panel — 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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-16",
|
||
"type": "task",
|
||
"parent_id": "T-6",
|
||
"title": "problems panel — surface pql doctor + validate warnings",
|
||
"status": "done",
|
||
"priority": "medium",
|
||
"decision_ref": "D-3",
|
||
"created_at": "2026-04-22 11:47:18",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-17",
|
||
"type": "task",
|
||
"parent_id": "T-8",
|
||
"title": "add dart doc generation to CI",
|
||
"description": "Wire `dart doc` into the CI pipeline so the public API surface (everything in lib/*.dart barrel exports) renders to HTML on every push to main.\n\n**Acceptance:**\n- New CI step in `.gitea/workflows/test.yml` runs `dart doc lib/` and uploads the generated `doc/api/` artefact.\n- Documentation comments on public APIs are validated (no broken references); `dart doc --validate-links` part of the run.\n- Generated docs published somewhere browsable — Gitea Pages, GitHub Pages once the repo moves, or an S3-style static host (decision deferred until Gitea actions land per D-32).\n\n**Not in scope:** writing missing docstrings (separate sweep), private-API docs, package.json-style metadata for pub.dev (we never publish to pub.dev — single binary).",
|
||
"status": "done",
|
||
"priority": "medium",
|
||
"decision_ref": "D-32",
|
||
"created_at": "2026-04-22 13:26:29",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-18",
|
||
"type": "task",
|
||
"title": "audit error handling in PTY and IPC critical paths",
|
||
"status": "done",
|
||
"priority": "medium",
|
||
"decision_ref": "D-5",
|
||
"created_at": "2026-04-22 13:26:29",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-19",
|
||
"type": "task",
|
||
"parent_id": "T-2",
|
||
"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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-20",
|
||
"type": "task",
|
||
"parent_id": "T-2",
|
||
"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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-21",
|
||
"type": "task",
|
||
"parent_id": "T-4",
|
||
"title": "implement welcome screen per hi-fi design",
|
||
"status": "done",
|
||
"priority": "medium",
|
||
"decision_ref": "D-43",
|
||
"created_at": "2026-04-22 14:08:40",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-22",
|
||
"type": "task",
|
||
"parent_id": "T-4",
|
||
"title": "multi-buffer editor tabs",
|
||
"status": "cancelled",
|
||
"priority": "medium",
|
||
"decision_ref": "D-6",
|
||
"created_at": "2026-04-22 14:08:40",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-23",
|
||
"type": "task",
|
||
"parent_id": "T-4",
|
||
"title": "wire command palette keybinding",
|
||
"description": "D-48 names `⌘P` (fuzzy file open) and `⌘⇧P` (command palette) as the canonical keyboard navigation. The command palette overlay/widget exists; the keybinding is not yet wired.\n\n**Acceptance:**\n- `⌘⇧P` (`Ctrl+Shift+P` on Linux, follows the kernel keymap normalization) opens the command palette overlay over the active workspace.\n- Esc dismisses; Enter runs the highlighted command; arrow keys move the highlight.\n- Commands listed are everything registered via `CommandContribution` across all activated extensions.\n- Fuzzy match against command title; recent / pinned commands float to the top.\n\n**Implementation hints:**\n- Slot exists: `Slots.commandPalette` is reserved (lib/kernel/src/panels/slot_id.dart).\n- Keybinding goes in `lib/kernel/src/commands/keybindings.dart` per the D-54 keymap.\n- The overlay should not shift layout (D-48 chrome budget — no layout shift on palette open).",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"decision_ref": "D-6",
|
||
"created_at": "2026-04-22 14:08:40",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-24",
|
||
"type": "task",
|
||
"parent_id": "T-3",
|
||
"title": "secondary Claude pane UI wiring",
|
||
"status": "done",
|
||
"priority": "medium",
|
||
"decision_ref": "D-41",
|
||
"created_at": "2026-04-22 14:08:40",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-25",
|
||
"type": "task",
|
||
"parent_id": "T-8",
|
||
"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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-26",
|
||
"type": "task",
|
||
"parent_id": "T-8",
|
||
"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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-27",
|
||
"type": "task",
|
||
"parent_id": "T-8",
|
||
"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 — 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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-28",
|
||
"type": "task",
|
||
"parent_id": "T-8",
|
||
"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": "done",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-22 20:14:52",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-29",
|
||
"type": "story",
|
||
"parent_id": "T-4",
|
||
"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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-31",
|
||
"type": "story",
|
||
"title": "Implement focus mode (full-window takeover)",
|
||
"description": "Focus mode via double-click header, hover ⛶ glyph, or ⌘. 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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-33",
|
||
"type": "story",
|
||
"title": "Wire canonical keyboard shortcuts from interaction model",
|
||
"description": "Add commands and keybindings: ⌘⇧1/⌘⇧3 collapse, ⌘1/⌘2/⌘3 focus panel, ⌘. focus mode, ⌥1-5 left section switch, ⌘E editor, ⌘W close editor, Esc dismiss.",
|
||
"status": "done",
|
||
"priority": "high",
|
||
"decision_ref": "D-54",
|
||
"created_at": "2026-04-22 20:34:01",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-35",
|
||
"type": "story",
|
||
"parent_id": "T-4",
|
||
"title": "Implement editor-above-Claude mode in middle column",
|
||
"description": "⌘E opens editor above Claude with draggable divider, 30-40% height. ⌘W closes. Viewer ↔ editor swap: ✎ promotes to editor, 👁 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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-36",
|
||
"type": "story",
|
||
"parent_id": "T-7",
|
||
"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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-37",
|
||
"type": "task",
|
||
"parent_id": "T-6",
|
||
"title": "Contribute tickets \u0026 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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-38",
|
||
"type": "task",
|
||
"parent_id": "T-6",
|
||
"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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-39",
|
||
"type": "task",
|
||
"parent_id": "T-6",
|
||
"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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-40",
|
||
"type": "task",
|
||
"parent_id": "T-8",
|
||
"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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-41",
|
||
"type": "task",
|
||
"parent_id": "T-7",
|
||
"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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-42",
|
||
"type": "task",
|
||
"parent_id": "T-5",
|
||
"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-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-43",
|
||
"type": "story",
|
||
"parent_id": "T-3",
|
||
"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": "done",
|
||
"priority": "high",
|
||
"decision_ref": "D-41",
|
||
"created_at": "2026-04-22 22:01:13",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-44",
|
||
"type": "story",
|
||
"title": "image paste in Claude panel prompt bar",
|
||
"description": "Support pasting images (Ctrl+V / right-click paste) into the Claude panel text input bar. The image should be sent as part of the message context to Claude, matching Claude Code CLI multimodal input.",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-23 20:25:26",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-45",
|
||
"type": "task",
|
||
"parent_id": "T-3",
|
||
"title": "detect and install tmux on first launch",
|
||
"description": "On first launch, check if tmux is on PATH. If missing, prompt the user with platform-appropriate install instructions (apt, dnf, brew) or offer to install automatically. Claude pane requires tmux per D-41; without it the primary session cannot persist.",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-23 20:27:28",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-46",
|
||
"type": "epic",
|
||
"title": "cross-platform clide installer",
|
||
"description": "Build a distribution-aware installer for clide. Detects platform (Linux distro, macOS, WSL) and package manager (apt, dnf, pacman, brew, winget). Handles: Flutter runtime or bundled AOT binary, ptyc compilation or prebuilt binary, tmux and other runtime dependencies, font installation (Josefin Sans, JetBrains Mono, Phosphor Icons), desktop entry / .app bundle / Start Menu shortcut, and PATH registration. Should support both interactive install and headless CI mode (--unattended). Consider AppImage/Flatpak for Linux, .dmg for macOS.",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-23 20:27:58",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-47",
|
||
"type": "story",
|
||
"parent_id": "T-46",
|
||
"title": "clide self-update mechanism",
|
||
"description": "Check for new versions on startup (or on demand via command palette). Show a non-intrusive notification when an update is available. Support in-place update without losing running Claude sessions (tmux sessions survive). Respect POLICY.md: no silent network calls on default launch path — the check should be opt-in or gated behind a setting. Consider delta updates for bandwidth efficiency.",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-23 20:28:43",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-48",
|
||
"type": "story",
|
||
"title": "application menu bar (Zed-style)",
|
||
"description": "Add a Zed-style application menu integrated into the hat bar: File, Edit, Selection, View, Help/About. Rendered as custom Flutter widgets (no native menu bar — we own the chrome per D-7). Menus expose the same commands registered in the command palette with their keybindings shown inline. Submenus for View should include panel toggles, zoom, focus mode. About opens a modal with version, license, and links.",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-23 20:30:09",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-49",
|
||
"type": "story",
|
||
"title": "native folder picker for Open Workspace",
|
||
"description": "Replace the text input in the welcome screen Open Workspace flow with a native folder picker dialog (gtk_file_chooser on Linux, NSOpenPanel on macOS). Fall back to the text input if the native dialog is unavailable. The selected path should validate as a git repo before opening.",
|
||
"status": "done",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-23 20:31:35",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-50",
|
||
"type": "story",
|
||
"title": "toast notification system",
|
||
"description": "Non-modal toast notifications for operation feedback (git push succeeded, extension activated, update available, errors). Slide in from bottom-right or top-right, auto-dismiss after timeout, manually dismissable. Queue multiple toasts. Severity levels map to status tokens (success/warning/error/info).",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-23 20:31:48",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-51",
|
||
"type": "story",
|
||
"title": "quick file open (Ctrl+P)",
|
||
"description": "Fuzzy file finder overlay, separate from the command palette. Wired to pql files with debounced input. Shows file path with match highlighting. Enter opens in editor or markdown viewer depending on file type. Recent files shown when input is empty.",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-23 20:32:06",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-52",
|
||
"type": "story",
|
||
"title": "find in files (Ctrl+Shift+F)",
|
||
"description": "Workspace-wide text search via ripgrep or grep fallback. Sidebar panel with search input, match-in-context results, clickable to open file at line. Supports regex toggle, case sensitivity toggle, include/exclude glob filters.",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-23 20:32:06",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-53",
|
||
"type": "story",
|
||
"title": "search and replace across files",
|
||
"description": "Companion to find-in-files. Preview all replacements before applying. Support regex capture groups in replacement. Respects ignore patterns from pql config.",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-23 20:32:06",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-54",
|
||
"type": "story",
|
||
"title": "output and log panel",
|
||
"description": "Bottom panel or context tab showing daemon logs, build output, extension logs, and pql sync output. Filterable by source. Auto-scrolls to latest. Useful for debugging extension and IPC issues.",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-23 20:32:06",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-55",
|
||
"type": "story",
|
||
"title": "system tray and dock persistence",
|
||
"description": "Minimize to system tray on Linux (AppIndicator) or Dock on macOS. Reopening from tray restores the window without cold boot. tmux sessions stay alive in background regardless.",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-23 20:32:06",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-56",
|
||
"type": "story",
|
||
"title": "deep link protocol handler",
|
||
"description": "Register clide:// protocol handler. Support clide://open?path=/repo/file.md\u0026line=42 for external tools to open files at specific locations. Useful for CI links, error reports, and cross-tool integration.",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-23 20:32:06",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-57",
|
||
"type": "task",
|
||
"title": "drag tab reordering in sidebar and context panel",
|
||
"description": "Reorder tabs in the sidebar and context panel icon rails by dragging. Persist order to project settings.",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-23 20:32:06",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-58",
|
||
"type": "task",
|
||
"title": "activity badges on sidebar icons",
|
||
"description": "Show unread count dots on sidebar icons: git changes count, new problems, pending notifications. Badges clear when the tab is viewed.",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-23 20:32:06",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-59",
|
||
"type": "task",
|
||
"title": "progress indicators in status bar",
|
||
"description": "Loading bar or spinner in the status bar for long-running operations: git pull/push, pql sync, extension install. Operations register with the kernel; status bar renders the active set.",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-23 20:32:06",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-60",
|
||
"type": "story",
|
||
"title": "workspace trust prompt",
|
||
"description": "Prompt before running extensions or loading project settings in untrusted repositories. Trust decision persisted per repo path. Untrusted mode disables third-party extensions and restricts IPC commands.",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-23 20:32:06",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-61",
|
||
"type": "task",
|
||
"title": "kernel scheduler service with tiered timer events",
|
||
"description": "Add SchedulerService to the kernel with Timer.periodic instances at five tiers (1min, 10min, 15min, 60min, midnight). Emits SchedulerTick events on the DaemonBus. Extensions subscribe and filter by tier.",
|
||
"status": "done",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-24 06:34:16",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-62",
|
||
"type": "story",
|
||
"title": "auto-refresh decisions and tickets on file change and scheduler tick",
|
||
"description": "Decisions panel subscribes to files.changed for decisions/*.md and to the 10-minute scheduler tick. Tickets panel subscribes to files.changed for tickets/ paths and to the 10-minute tick. Both use debounce to coalesce burst events. Keep existing data visible during refresh.",
|
||
"status": "done",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-24 06:34:16",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-63",
|
||
"type": "task",
|
||
"title": "manual refresh button in sidebar panels",
|
||
"description": "Add a small arrowClockwise icon button next to the filter box in decisions, tickets, and pql markdown panels. Triggers immediate data reload. Disabled while refresh is in-flight.",
|
||
"status": "done",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-24 06:34:16",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-64",
|
||
"type": "story",
|
||
"title": "VS Code keybinding preset",
|
||
"description": "Ship a VS Code-compatible keybinding preset that maps standard VS Code shortcuts to clide commands. Users select it in settings. Covers file navigation, editor actions, panel toggles, search, and terminal.",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-24 06:34:16",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-65",
|
||
"type": "story",
|
||
"title": "Vim keybinding preset",
|
||
"description": "Ship a Vim-compatible keybinding preset with modal editing support (normal/insert/visual modes). Maps Vim motions and commands to clide editor and navigation actions. Users select it in settings.",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-24 06:34:16",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-66",
|
||
"type": "story",
|
||
"title": "JetBrains keybinding preset",
|
||
"description": "Ship a JetBrains/IntelliJ-compatible keybinding preset mapping standard JetBrains shortcuts to clide commands. Covers navigation, refactoring, search, run/debug, and tool windows.",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-24 06:34:16",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-67",
|
||
"type": "story",
|
||
"title": "review Semantics widget coverage and UI testability",
|
||
"description": "Audit all interactive widgets for Semantics coverage (labels, roles, states). Verify flutter test can locate and interact with every panel, button, and input via find.bySemanticsLabel. Run the existing a11y test suite and document gaps. Target: every user-facing action is testable without widget keys.",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"created_at": "2026-04-24 06:34:16",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-68",
|
||
"type": "task",
|
||
"title": "inline terminal emulator (drop xterm.dart dependency)",
|
||
"status": "done",
|
||
"priority": "medium",
|
||
"created_at": "2026-05-05 12:52:48",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-69",
|
||
"type": "story",
|
||
"title": "Claude pane: fullscreen mode + mouse-wheel scroll",
|
||
"status": "done",
|
||
"priority": "medium",
|
||
"created_at": "2026-05-05 12:52:52",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-70",
|
||
"type": "bug",
|
||
"title": "Bold text breaks terminal cell grid (workaround: suppress bold)",
|
||
"status": "done",
|
||
"priority": "medium",
|
||
"created_at": "2026-05-05 12:52:56",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-71",
|
||
"type": "task",
|
||
"title": "PTY read buffer: 4KB → 64KB",
|
||
"status": "done",
|
||
"priority": "medium",
|
||
"created_at": "2026-05-05 12:53:01",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-72",
|
||
"type": "task",
|
||
"title": "tmux: isolated -L clide socket so user's tmux config doesn't bleed into clide",
|
||
"status": "done",
|
||
"priority": "medium",
|
||
"created_at": "2026-05-05 12:53:05",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-73",
|
||
"type": "task",
|
||
"title": "ship matching bold weight to restore semantic bold rendering",
|
||
"description": "Workaround in T-70 / `lib/src/terminal/src/ui/painter.dart` suppresses bold rendering entirely because Flutter`s synthetic bold drifts glyph advance widths and breaks the monospace cell grid. We lose semantic bold (bash prompts, emphasis in TUIs) as a side effect.\n\n**Acceptance:**\n- A bold-weight monospace face ships alongside the regular weight, with verified-equal advance widths at clide`s render size (cell-grid stable on bold).\n- The painter`s `bold: false` override is reverted; bold attributes from terminal escapes render visually bold.\n- Test: render a known-mixed-weight buffer and assert paragraph maxIntrinsicWidth matches the regular-weight measurement (cell drift = 0).\n\n**Options to evaluate:**\n- Re-add JetBrainsMono-Bold.ttf once we`ve verified its advance widths actually equal the Regular file at our cellSize. (We dropped it because the synthetic-bold issue obscured whether the bundled Bold actually matched.)\n- Switch to a variable JetBrainsMono and request a specific weight axis position.\n- Pick a different mono font whose Bold has guaranteed-equal advance (Berkeley Mono, IBM Plex Mono).\n\n---\nRefinement (2026-05-06):\n\nInvestigation showed the perceived over-bolding in the Claude pane was synthetic-bold smearing (Flutter overpaints when no Bold.ttf is registered), not aggressive-but-legitimate bold. Visual A/B confirms a real bold face renders crisp emphasis without smearing. No per-pane renderer config needed.\n\n**Concrete fix shape:**\n1. `pubspec.yaml` (family JetBrainsMono): register `JetBrainsMono-Bold.ttf` with `weight: 700` and `JetBrainsMono-BoldItalic.ttf` with `weight: 700, style: italic`. Files are already on disk under `assets/fonts/jetbrains_mono/`.\n2. `assets/licenses.yaml` JetBrainsMono entry: update `weights_bundled` to `[Regular, Italic, Bold, BoldItalic]` per D-42 (must match what is actually wired into the font family).\n3. `lib/src/terminal/src/ui/painter.dart:198`: revert the `bold: false` override and delete the now-stale comment block at lines 193-195.\n4. Test: render a mixed-weight buffer and assert `paragraph.maxIntrinsicWidth` matches the all-regular measurement (cell drift = 0). If parity fails, the fallback options stay open (variable JetBrainsMono / Berkeley Mono / IBM Plex Mono).\n\nWorkaround comment (`Skip bold rendering — Flutter`s synthetic bold...`) goes away in step 3.",
|
||
"status": "done",
|
||
"priority": "medium",
|
||
"created_at": "2026-05-05 12:53:21",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-74",
|
||
"type": "task",
|
||
"title": "forward real mouse events to TUI apps (not just PgUp/PgDown)",
|
||
"description": "Mouse wheel scroll is currently translated to PgUp/PgDown key input in `lib/src/terminal/src/terminal_view.dart` as a pragmatic workaround — TUIs that capture mouse events without binding the wheel get a no-op otherwise. The downside: any TUI that does its own wheel handling (selection, click-to-position) loses the actual mouse coordinates because all we send is keyboard.\n\n**Acceptance:**\n- The terminal forwards `PointerScrollEvent` and click/drag/release as proper xterm mouse-protocol escape sequences (CSI `M` / SGR variant per the report mode the inner program declared via `?1000h` / `?1006h`).\n- Tested with `vim` (mouse mode `a`), `htop`, `less` — all should respond to mouse selection / click correctly.\n- Falls back to PgUp/PgDown only when the inner program has not declared a mouse mode.\n- The `mouseInput` API on `Terminal` is wired through `Listener.onPointerSignal` instead of being bypassed.\n\n**Why this isn`t the default already:** the bypass was the cheapest fix to get scroll working at all when the multitab/Scrollable ate scroll events. With those fixes landed, the right-shaped mouse forwarding is the proper next step.",
|
||
"status": "ready",
|
||
"priority": "medium",
|
||
"created_at": "2026-05-05 12:53:22",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-75",
|
||
"type": "task",
|
||
"title": "PTY: surface errno from forkpty/execve/write/ioctl failures",
|
||
"status": "done",
|
||
"priority": "high",
|
||
"created_at": "2026-05-05 12:58:59",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-76",
|
||
"type": "task",
|
||
"title": "PTY: fix resource leaks and reader-isolate races",
|
||
"status": "done",
|
||
"priority": "high",
|
||
"created_at": "2026-05-05 12:58:59",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-77",
|
||
"type": "task",
|
||
"title": "IPC server: per-request timeout, broadcast logging, stale-socket race",
|
||
"status": "done",
|
||
"priority": "high",
|
||
"created_at": "2026-05-05 12:58:59",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-78",
|
||
"type": "bug",
|
||
"title": "files.read path traversal — validate paths stay under workspace root",
|
||
"status": "done",
|
||
"priority": "high",
|
||
"created_at": "2026-05-05 12:58:59",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-79",
|
||
"type": "task",
|
||
"title": "pane.spawn / editor.open: map errno to actionable error kinds",
|
||
"status": "done",
|
||
"priority": "medium",
|
||
"created_at": "2026-05-05 12:58:59",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-80",
|
||
"type": "task",
|
||
"title": "Standardize errno constants + logger in PTY/IPC/daemon",
|
||
"description": "From the PTY/IPC error-handling audit (T-18, see docs/audits/pty-ipc-error-handling-2026-05-05.md). Two cleanup items rolled together:\n\n**Errno constants (audit item #23):**\n- Magic numbers (`4` for EINTR, `9` for EBADF, `28` for SIGWINCH, `1` for SIGHUP, `32` for EPIPE) appear inline across `lib/src/pty/session.dart` and `lib/src/pty/native_pty.dart`.\n- Centralize them in `lib/src/pty/errors.dart` or a sibling `posix.dart` as named constants.\n- Existing `lib/src/ipc/errno_mapping.dart` already has a `PosixErrno` class — extend it or move to a shared location both layers import from.\n\n**Logger standardization (audit item #22, partial #26):**\n- `lib/src/ipc/server.dart` uses `stderr.writeln(...)` directly; the rest of the daemon either uses no logger or a custom one.\n- The Flutter-host process often consumes stderr, so log lines disappear silently.\n- Pick one logger interface (kernel `log` already exists for the app side), wire `DaemonServer` and the daemon-side handlers to use it.\n- Dispatch error messages should prefix with the request `cmd` so log correlation works (audit item #26).\n\n**Out of scope for this ticket:** changes to log-LEVEL policy, log retention, log files vs stderr — pure substitution job.",
|
||
"status": "backlog",
|
||
"priority": "low",
|
||
"created_at": "2026-05-05 12:58:59",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-81",
|
||
"type": "task",
|
||
"title": "Misc PTY/IPC/git polish (audit medium-priority items)",
|
||
"description": "Catch-all for the medium-priority items from the PTY/IPC error-handling audit (T-18, see docs/audits/pty-ipc-error-handling-2026-05-05.md) that didn`t earn dedicated tickets:\n\n- **#17** — `files.read` `readAsStringSync` is unguarded; UTF-8 errors / permissions / mid-read deletion become 500-style dispatch errors. Wrap in try/catch and emit a clean `IpcResponse.err`.\n- **#19** — `PtySession.close` swallows the 500ms timeout silently (`onTimeout: () {}`). Log when the timeout fires so we know SIGKILL was needed.\n- **#20** — Reader isolate treats every negative `read()` return that isn`t EINTR as EOF. Distinguish EBADF/EIO (real EOF) from transient EAGAIN (recoverable) and log the latter.\n- **#21** — `scm_rights.dart` reads cmsg-data fd without verifying `dataOffset + 4 \u003c= msgControllen`. Bounds check before deref so a malformed peer can`t feed garbage as an fd.\n- **#25** — `_gitError` in `lib/src/daemon/git_commands.dart` always reports `tool_error`; push rejections / merge conflicts should map to `IpcExitCode.conflict` when stderr matches known patterns.\n- **#27** — `pane.spawn` returns `ok` even when `registry.write(id, bytes)` returned `n == -1`. Distinguish the failure.\n- **#28** — `IpcResponse.fromJson` throws `TypeError` on a malformed peer response missing `error`. Graceful degrade.\n- **#29** — PATH resolution in `native_pty.dart` uses the first existing match without `X_OK` check; non-executable files shadow valid binaries further along PATH.\n\nLand each as a small focused commit; ticket closes when all items above are merged.",
|
||
"status": "backlog",
|
||
"priority": "low",
|
||
"created_at": "2026-05-05 12:58:59",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-82",
|
||
"type": "task",
|
||
"title": "add Catppuccin theme to bundled themes",
|
||
"description": "User request: ship a Catppuccin theme alongside the four bundled themes (clide, midnight, paper, terminal — D-44).\n\n**Acceptance:**\n- New theme YAML at `lib/kernel/src/theme/themes/catppuccin.yaml` (or split for the four Catppuccin flavours: latte / frappe / macchiato / mocha — pick one or all four; recommend Mocha as the dark default and Latte for the light variant).\n- Tokens populated for every key in `SurfaceTokens` (lib/kernel/src/theme/tokens.dart) — chrome, panels, tabs, list items, status colours, syntax tokens (D-45).\n- WCAG-AA contrast gate (D-22) passes against the theme.\n- Listed in `assets/licenses.yaml` with the upstream license (MIT) and attribution.\n- Theme picker (`builtin.theme-picker`) shows it in the live-switch menu.\n\n**Source palettes:** https://github.com/catppuccin/catppuccin#-palette — official hex values, don`t paraphrase.\n\n**Why now:** test user request; also a good exercise for any future user-contributed theme since Catppuccin is the most common one to ask for.",
|
||
"status": "backlog",
|
||
"priority": "low",
|
||
"created_at": "2026-05-06 08:30:15",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-83",
|
||
"type": "task",
|
||
"title": "design reusable sortable tab system for multitab panes",
|
||
"status": "done",
|
||
"priority": "high",
|
||
"created_at": "2026-05-06 09:17:39",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-84",
|
||
"type": "task",
|
||
"title": "MultitabPane: drag-to-reorder gesture wiring",
|
||
"status": "done",
|
||
"priority": "medium",
|
||
"created_at": "2026-05-06 10:16:02",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-85",
|
||
"type": "task",
|
||
"title": "consolidate theme-ui into ui-design skill with theme/surface/geometry/icons references",
|
||
"status": "done",
|
||
"priority": "medium",
|
||
"created_at": "2026-05-06 13:28:58",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-86",
|
||
"type": "task",
|
||
"title": "codify UI spacing constants (gaps, paddings, control sizes)",
|
||
"status": "done",
|
||
"priority": "medium",
|
||
"created_at": "2026-05-06 13:28:58",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-87",
|
||
"type": "bug",
|
||
"title": "kill secondary Claude tmux sessions on tab close and on app shutdown",
|
||
"description": "D-41 lifecycle contract: \"closing a secondary kills that tmux session\" and \"secondary numbering (-1, -2, ...) resets between clide runs since ephemeral state is lost\" — the second clause only holds if non-primary tmux sessions are reaped on app shutdown.\n\nNeither happens today.\n\n**Gap 1 — tab close is UI-only.** `ClaudeSessionHostState` (lib/builtin/claude/src/claude_session_host.dart:18) builds tabs via MultitabController; closing a secondary just removes the entry. No `tmux kill-session` shellout anywhere in lib/builtin/claude/ or lib/src/pty/ (verified by grep for `kill-session` / `killSession` / `tmux kill`).\n\n**Gap 2 — app shutdown leaks secondaries.** Nothing reaps `clide-claude-\u003crepohash\u003e-N` sessions when the app exits. Repro: spawn two secondaries, quit clide, `tmux -L clide list-sessions` still shows them. Confirmed live: 4 sessions on the clide socket (primary + -1, -2, -3) survived prior runs.\n\n**Acceptance:**\n- Closing a secondary tab in the Claude pane runs `tmux -L clide kill-session -t \u003cname\u003e` for that secondary's session name (use existing `secondarySessionName(repoRoot, n)`).\n- App shutdown enumerates `clide-claude-\u003crepohash\u003e-*` sessions on the clide socket and kills the non-primary ones. Primary is left detached (D-41 explicit).\n- `_nextSecondary` index policy: keep the in-process counter for the running app; cold-start always begins at 1 because secondary sessions are gone.\n- Add a test that exercises the close path and asserts the kill-session command was invoked (mock the tmux runner).\n\n**Out of scope:** primary lifecycle (unchanged — survives shutdown by design), general-purpose terminal panes (no tmux wrapping per D-41), Windows behaviour.",
|
||
"status": "done",
|
||
"priority": "high",
|
||
"decision_ref": "D-41",
|
||
"created_at": "2026-05-06 13:46:07",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-88",
|
||
"type": "task",
|
||
"title": "track dugite-native upstream releases for security updates",
|
||
"description": "D-59 (bundled git via dugite-native) costs out: \"Must track dugite-native releases for security updates.\" Up to today this lived only in the D-record prose with no tracking — clean-house v1 flagged it as RULE-SUNSET-WITHOUT-TICKET.\n\n**Acceptance:**\n- A periodic check (manual or scheduled) compares the bundled `native/dugite/` SHA against the latest release at https://github.com/desktop/dugite-native/releases.\n- When a new release lands with a CVE label or fixes a CVE-tagged git release, schedule a bump; otherwise track the version drift.\n- Bumps follow D-63: rebuild with the new SHA, update `BUILD.md`, update `assets/licenses.yaml` if the dugite or git version changed, refresh the binary in `native/dugite/`.\n\n**Periodicity:** quarterly check is enough during normal operation; faster on git CVE announcements (subscribe to https://github.com/git/git/security/advisories, https://github.com/desktop/dugite-native/security/advisories).\n\n**Out of scope:** the rebuild itself is T-25 (CI reproducible build for libtree-sitter.so) — same shape applies to dugite. This ticket is the calendar; T-25 is the machine.",
|
||
"status": "backlog",
|
||
"priority": "low",
|
||
"decision_ref": "D-59",
|
||
"created_at": "2026-05-06 14:34:35",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-89",
|
||
"type": "epic",
|
||
"title": "drive line coverage from 35% to 95%",
|
||
"description": "Today: total line coverage **34.9%** (2984/8549 lines). Per area:\n\n| area | hit / total | % |\n|---|---|---|\n| `lib/src/terminal/` | 12 / 3167 | **0.4%** |\n| `lib/widgets/` | 537 / 1184 | 45.4% |\n| `lib/src/pql/` | 45 / 94 | 47.9% |\n| `lib/builtin/` | 269 / 550 | 48.9% |\n| `lib/kernel/` | 889 / 1691 | 52.6% |\n| `lib/src/daemon/` | 320 / 584 | 54.8% |\n| `lib/src/pty/` | 130 / 216 | 60.2% |\n| `lib/src/git/` | 356 / 518 | 68.7% |\n| `lib/src/ipc/` | 110 / 156 | 70.5% |\n| `lib/src/files/` | 98 / 133 | 73.7% |\n| `lib/extension/` | 41 / 54 | 75.9% |\n| `lib/src/editor/` | 107 / 129 | 82.9% |\n| `lib/src/panes/` | 70 / 73 | 95.9% |\n\nThe dominant hole is `lib/src/terminal/` — the terminal emulator code. Header still credits xterm.dart upstream, but per CLAUDE.md (\"Own the rendering stack\") and the explicit guidance attached to this epic: in-tree code is owned, no carve-outs, same coverage bar.\n\n**End state:**\n- Total line coverage ≥ 95% (no per-area carve-outs).\n- Pre-push gate hard-fails on coverage drops via a committed floor (`coverage/floor.txt`); floor ratchets up only.\n- Per D-66 the gate lands at the *current* floor in the first child ticket; subsequent floor bumps come with the test-writing children.\n\n**Acceptance criteria for closing this epic:**\n1. `coverage/floor.txt` reads `95` (or higher).\n2. `flutter test --coverage --exclude-tags forkpty` produces ≥95% line coverage.\n3. The pre-push gate computes coverage and rejects pushes below the floor.\n4. No new `// ignore:` / `// ignore_for_file:` / analysis-options excludes were added to dodge the gate. Any suppression added during this campaign needs explicit approval and an inline reason at the suppression site.\n\n**Strategy / child shape:**\nThe gate plumbing lands first so future test additions are visible as floor bumps. Then per-area sweeps — biggest holes first because they move the global number fastest, but each area is its own child ticket with its own scope.\n\n1. **Gate plumbing** (first child) — install the ratchet, lock in current floor, expose `make coverage-gate` and wire it into `make push-check`.\n2. **`lib/src/terminal/` sweep** (second child, by far the largest) — likely splits into sub-children once the area's structure is mapped (parser, buffer, painter, gesture handling, mouse reporting, escape sequences, cell-grid measurement, etc.).\n3. **Per-area sweeps** — claim a child ticket when starting an area; close when the area hits 95%. Order by impact: terminal → widgets → pql → builtin → kernel → daemon → pty → git → ipc → files → extension → editor.\n4. **Final ratchet** — once total ≥ 95%, set floor to 95 and close the epic.\n\n**Out of scope:**\n- Branch coverage (lcov data is weak for branch on Dart; line coverage is the contract).\n- Integration / E2E coverage (only widget + unit feed lcov).\n- `lib/test_app.dart` exclusion: verify in the gate child whether Dart's coverage tooling already excludes it; if not, decide explicitly rather than carve it out silently.\n\n**Cross-references:** D-66 (this epic's decision record), D-29 (pre-push gate scope), \"Own the rendering stack\" guardrail (CLAUDE.md).\n",
|
||
"status": "in_progress",
|
||
"priority": "high",
|
||
"decision_ref": "D-66",
|
||
"created_at": "2026-05-06 20:36:42",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-90",
|
||
"type": "task",
|
||
"parent_id": "T-89",
|
||
"title": "install ratcheting coverage gate at current floor",
|
||
"description": "First child of T-89. Locks in the no-regression contract before any test-writing children land — so subsequent floor bumps are explicit and reviewable.\n\n**What lands:**\n\n1. **`coverage/floor.txt`** — committed file containing today's actual line-coverage number (currently `34` after rounding down from 34.9% to give a small margin against measurement jitter; the gate child re-measures on first install and writes the exact number it observes minus 1 if needed).\n2. **`ci/coverage_gate.sh`** — runs `flutter test --coverage --exclude-tags forkpty`, parses `coverage/lcov.info` (sum of LH / sum of LF * 100), compares to `coverage/floor.txt`, exits non-zero if below, prints the actual percentage either way.\n3. **`Makefile` `coverage-gate` target** — wraps `ci/coverage_gate.sh` for local use.\n4. **`make push-check`** — adds `coverage-gate` to its dependency list. Pre-push hook (already wired via `.githooks/pre-push`) picks this up automatically.\n5. **`coverage/README.md`** — short note explaining the floor file, how to bump it (rule: bump only in the same commit that adds covering tests, with the bump reflecting the measured improvement), and the link to D-66.\n\n**Acceptance:**\n- `make coverage-gate` passes on a clean checkout (floor matches reality).\n- `make push-check` includes the gate.\n- A test deletion would fail `make coverage-gate` (verify by experiment, not just by reading the script — invariant has to actually hold).\n- Pre-push hook runs the gate (verify by attempting a deliberately-failing push and seeing it blocked).\n- `coverage/lcov.info` is gitignored (it's regenerated on every run; only `floor.txt` is committed).\n\n**Out of scope:**\n- Bumping the floor toward 95% — that's the test-writing children's job.\n- Branch coverage — D-66 explicitly excludes.\n- Per-area floors — D-66 chose a single global floor for simplicity.\n\n**Notes:**\n- `lcov` CLI is not installed on this dev machine; parser must be self-contained (awk or python). Don't introduce `lcov` as a dependency of the gate.\n- `coverage/lcov.info` is the standard Dart output path of `flutter test --coverage`. Don't relocate.\n- `flutter test --coverage` runs all tests including `forkpty`-tagged ones, which fail under the flutter test runner (see comment in `test/pty/session_test.dart`); `--exclude-tags forkpty` matches `ci/test.sh`. Forkpty-tagged tests don't currently feed lcov because they run via `dart test`; check whether their coverage can be merged later (separate ticket if not).\n",
|
||
"status": "done",
|
||
"priority": "high",
|
||
"decision_ref": "D-66",
|
||
"created_at": "2026-05-06 20:38:14",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-91",
|
||
"type": "task",
|
||
"parent_id": "T-89",
|
||
"title": "test sweep: drive lib/src/terminal/ to 95%",
|
||
"description": "Second child of T-89. The dominant coverage hole — `lib/src/terminal/` is 12 / 3167 lines (0.4%), about 56% of the global gap.\n\n**Today's distribution within `lib/src/terminal/src/`:**\n\n| sub-area | hit / total | % | shape |\n|---|---|---|---|\n| `core/buffer/` | 0 / 581 | 0.0% | pure data structures |\n| `core/escape/` | 1 / 473 | 0.2% | pure parser logic |\n| `core/input/` | 4 / 292 | 1.4% | key + keytab tables |\n| `core/(root)` | 0 / 188 | 0.0% | callback wiring |\n| `core/mouse/` | 3 / 51 | 5.9% | encoder logic |\n| `ui/` | 3 / 823 | 0.4% | painters + render objects |\n| `(root)` | 0 / 471 | 0.0% | `Terminal` + `TerminalView` |\n| `utils/` | 1 / 251 | 0.4% | circular buffer etc. |\n\n**Strategy — pure logic first, then UI:**\n\n1. **`core/buffer/`** (581 lines, biggest single chunk, all pure Dart) — line/segment/cell models, scrollback math, range queries. Unit tests exercise the data structure directly.\n2. **`core/escape/`** (473 lines, pure state machine) — feed ANSI/VT byte streams in, assert the resulting buffer/cursor state. Reusable corpus from xterm.js / xterm.dart upstream is fair game.\n3. **`core/input/`** (292 lines) — key + keytab tables; assert mapping from logical key to escape bytes per terminal mode.\n4. **`core/mouse/`** (51 lines) — encoder for SGR-1006 / X11 mouse protocols. Small, fast.\n5. **`utils/`** (251 lines) — circular buffer + helpers; trivial unit tests.\n6. **`(root)`** (`terminal.dart`, `terminal_view.dart`, 471 lines) — orchestration; some unit-testable, some needs widget tests.\n7. **`ui/`** (823 lines) — painters and render objects; widget + golden tests. Hardest, most pixel-sensitive.\n\n**Acceptance:**\n- `lib/src/terminal/` line coverage ≥ 95%.\n- All tests fast (\u003c5 s for the area's test files combined; no real PTY shell-out).\n- Helper fixtures live in `test/terminal/_helpers/` if they get reused across files.\n- Per D-66, no `// ignore:` directives or analysis_options excludes added to dodge coverage.\n- `coverage_floor:` in `pubspec.yaml` is bumped in lockstep — every commit that adds covering tests bumps the floor by the measured delta (rounded down to the integer below the new percentage).\n\n**Strategy on commit cadence:**\nLand tests in batches per sub-area (one commit per `core/buffer/`, `core/escape/`, etc.). Each batch:\n- Adds tests under `test/terminal/\u003csub-area\u003e/`.\n- Re-measures coverage, bumps `coverage_floor:` to the new integer (or as high as the gate currently allows).\n- Documents in CHANGELOG under \"Changed\" (test-only changes can also fold into one CHANGELOG entry per area).\n\n**Out of scope:**\n- Refactoring `lib/src/terminal/` for testability — if a piece is genuinely untestable, surface it (don't silence it). Code under `lib/` is owned regardless of upstream attribution; refactor to make it testable rather than carving it out.\n- Branch coverage (D-66 explicitly excludes).\n- Mouse / keyboard *protocol* round-trips against real TUIs (that's T-74 and integration work).\n\n**Cross-references:** D-66 (coverage gate), T-89 (epic), T-90 (gate plumbing).\n",
|
||
"status": "in_progress",
|
||
"priority": "high",
|
||
"decision_ref": "D-66",
|
||
"created_at": "2026-05-06 20:49:47",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-92",
|
||
"type": "bug",
|
||
"title": "reflow tail-anchor reparenting can dangle on partially-filled lines",
|
||
"description": "**File:** `lib/src/terminal/src/core/reflow.dart` (`_LineReflow._addPart`).\n\n**Bug:** Anchors whose x-coordinate sits past the source line's `trimmedLength` get reparented onto whatever `_builder._result` is active at that moment. After the main reparenting loop has run with `lineFilled` on the final iteration, `_builder.take()` has already moved the filled line into `_lines` and reset `_result` to a fresh empty `BufferLine`. The post-loop block (lines 144-150 in the current source) then reparents the tail anchor onto that fresh empty line. If no further content lands in the builder before `finish()` exits — which is the case whenever the post-loop block fires on a non-wrapped logical line — the empty builder line is never appended to `_lines`, and the anchor ends up pointing to a `BufferLine` that the reflow result never includes.\n\n**Trigger paths in production code:**\n\n1. `SelectAllTextIntent` (`lib/src/terminal/src/ui/shortcut/actions.dart:60-63`) creates the end anchor at `x = terminal.viewWidth`. That is *exactly* the past-trimmedLength position for every partially-filled line in the buffer. Any user who Ctrl+A's and then resizes the terminal narrower hits the bug for every short line in the scrollback.\n2. `selectCharacters` / mouse drag selection (`lib/src/terminal/src/ui/render.dart:286-298`) — anchors are clamped to `[0, viewWidth-1]` but can sit past `trimmedLength` on a partially-filled line. Drag past content end + resize narrower trips the bug.\n3. `selectWord` (`render.dart:264-275`) — `getWordBoundary` walks until a separator OR `viewWidth`, so the end anchor of a near-edge word can land at `x = viewWidth` and trip the bug on resize.\n\n**Symptom:** selection extents lose track of content after a width change. Visible highlight stops moving with the underlying selection, or extents disappear off the rendered buffer entirely. No exception is thrown — the dangling anchor still has a valid `BufferLine` reference, it just isn't on a line in the visible buffer.\n\n**Acceptance:**\n- New unit test in `test/terminal/core/core_test.dart` (or a sibling) that:\n - Constructs a partially-filled source line and an anchor past `trimmedLength`,\n - Runs reflow shrink,\n - Asserts the anchor's owning line is *in the reflow output list*, not dangling.\n- The existing widget-level select-all behaviour still works on resize: a Ctrl+A across the buffer followed by a narrower resize keeps the selection's end anchor pointing to a line in the reflowed result.\n- No regression in the existing reflow tests (the tail-clamp cases, wide-char boundaries, multi-line wrapped runs).\n\n**Investigation notes (do not skip):**\n\nThe fix is conceptually small — the reparented tail anchor must land on a line that gets emitted. Two candidate shapes:\n\n- **Skip the reparent entirely.** Anchors past `trimmedLength` arguably shouldn't survive a shrink that drops their column. Risk: callers expect every anchor on the source to be reparented somewhere; silently disposing them changes semantics.\n- **Force `finish()` to flush the builder line.** Whenever the tail-anchor branch fired during `_addPart`, set a flag, and have `finish()` emit the builder line even if `isEmpty`. Risk: empty trailing lines start showing up in the reflow result, which would break callers that assume reflow returns only content lines (resize loop in `Buffer.resize` pads to viewport height after, so an extra empty might be benign there but worth checking).\n\nDon't pick one without re-reading the full reflow flow + every caller of `reflow()`. The selection round-trip is also tested at the widget layer indirectly — verify `make test-a11y` still passes after the change.\n\n**Surfaced from:** T-91 test sweep (commit 3196c49). The 100% coverage on reflow.dart was achieved by exercising this branch but explicitly *not* asserting on the resulting line being in the output — see the test comment.\n\n**Cross-references:** T-91 (epic parent for the test sweep that found this), T-89 (coverage epic), the test in `test/terminal/core/core_test.dart` group `reflow` named \"anchors on the source line tail (past trimmedLength) get reparented\" documents the current (buggy) behaviour.\n",
|
||
"status": "done",
|
||
"priority": "high",
|
||
"created_at": "2026-05-07 05:51:48",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-93",
|
||
"type": "bug",
|
||
"title": "TerminalView.onTapUp callback is wired to a dead path",
|
||
"description": "**File:** `lib/src/terminal/src/terminal_view.dart` (param + wiring) + `lib/src/terminal/src/ui/gesture/gesture_handler.dart` (pass-through) + `lib/src/terminal/src/ui/gesture/gesture_detector.dart` (dead field).\n\n**Bug:** `TerminalView.onTapUp` is documented as \"Callback for when the user taps on the terminal\" but is wired to a code path that is never executed:\n\n1. `TerminalView.build` passes `onTapUp: _onTapUp` to `TerminalGestureHandler` (terminal_view.dart:304).\n2. `TerminalGestureHandler.build` forwards via `onTapUp: widget.onTapUp` to `TerminalGestureDetector` (gesture_handler.dart:67).\n3. `TerminalGestureDetector._handleTapUp` (gesture_detector.dart:84) fires only `widget.onSingleTapUp`, never `widget.onTapUp`. The `onTapUp` field on `TerminalGestureDetector` (declared at line 31) is never invoked anywhere in the file.\n\nThe `_onTapUp` method on `TerminalViewState` itself is also dead — it's only referenced from the dead-field path above.\n\n**Two layers of \"dead\" here:**\n\n- The **wiring** is dead — nothing inside the terminal renderer calls into `widget.onTapUp` after the field is set.\n- The **public API surface** has zero in-tree callers. The single `TerminalView` callsite is `lib/widgets/src/clide_pty_view.dart`, and it doesn't pass any tap callback.\n\nSo no production code depends on this parameter; nothing observable changes regardless of which way it gets resolved. (TerminalView's `onSecondaryTapDown` / `onSecondaryTapUp` are wired correctly — `TapGestureRecognizer.onSecondaryTap*` fires them directly. Only `onTapUp` is bad.)\n\n**Two clean resolutions:**\n\n- **A. Fix the wiring.** Call `widget.onTapUp` from `_handleTapUp` (gesture_detector.dart:84) so the parameter does what its docstring claims. Risk: the existing `onSingleTapUp` semantics (gated on `!_isDoubleTap`) need a clearly-defined relationship to `onTapUp` — pick one of:\n - `onTapUp` fires on every tap up (raw); `onSingleTapUp` fires only on confirmed singles.\n - Collapse the two into a single hook and document the contract.\n- **B. Delete the dead surface.** Remove `onTapUp` from `TerminalView`, `TerminalGestureHandler`, and `TerminalGestureDetector` (plus the `_onTapUp` method on `TerminalViewState`). Cleanest because no caller is broken — the surface is documented but unused. If a future caller wants tap notifications, expose `onSingleTapUp` (which works) or re-add `onTapUp` *with wiring* at that point.\n\nRecommendation: **option B**. The \"remove unused public API\" pattern matches the no-pre-existing-excuse / dead-code cleanups in this campaign (the keytab-token defensive throws, the `_LineBuilder.isEmpty` getter, the `_buildDispatcher` rename). If a feature is needed later, add it back deliberately with tests and a real caller.\n\n**Acceptance:**\n\n- Either:\n - **A:** `TerminalView.onTapUp` fires on every tap up; new widget test in `test/terminal/terminal_view_test.dart` verifies the callback runs with a `CellOffset`. Document the contract on the docstring.\n - **B:** `TerminalView.onTapUp` parameter removed; `_onTapUp` method on `TerminalViewState` removed; `onTapUp` parameter on `TerminalGestureHandler` removed; `onTapUp` field + setter on `TerminalGestureDetector` removed. Existing tests still pass.\n- No regression in secondary-tap behaviour (those parameters are wired correctly via `TapGestureRecognizer.onSecondary*`).\n\n**Surfaced from:** T-91 test sweep on `terminal_view.dart`. The widget test that registered `onTapUp` and tapped via `tester.tap` confirmed the callback never fires regardless of `pumpAndSettle` / gesture flushing.\n\n**Cross-references:** T-91 (epic parent), T-89 (coverage epic).\n",
|
||
"status": "done",
|
||
"priority": "high",
|
||
"created_at": "2026-05-07 06:33:47",
|
||
"updated_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"id": "T-94",
|
||
"type": "task",
|
||
"title": "wire pql plan merge as git custom merge driver",
|
||
"status": "cancelled",
|
||
"priority": "medium",
|
||
"created_at": "2026-05-07 19:08:29",
|
||
"updated_at": "2026-05-07 19:08:52"
|
||
},
|
||
{
|
||
"id": "T-95",
|
||
"type": "bug",
|
||
"title": "TerminalGestureHandler tertiary-tap wired to secondary; middle-button mis-typed",
|
||
"description": "**File:** `lib/src/terminal/src/ui/gesture/gesture_handler.dart` (build wiring + state-method body) + `lib/src/terminal/src/ui/gesture/gesture_detector.dart` (dead public API).\n\n**Bug:** Middle-clicks (\"tertiary tap\") are routed through the wrong handler chain, and even the unreachable target carries a button-enum typo. Same shape as T-93 but on a different gesture pair.\n\nTrace:\n\n1. `TerminalGestureHandler.build` (gesture_handler.dart:65–82) wires:\n ```dart\n onTertiaryTapDown: onSecondaryTapDown,\n onTertiaryTapUp: onSecondaryTapUp,\n ```\n Right-hand sides are the state's `onSecondaryTapDown` / `onSecondaryTapUp` *methods* (lines 138–144), not the tertiary equivalents. So a middle-click reaches `_tapDown(widget.onSecondaryTapDown, details, TerminalMouseButton.right)` — i.e. fires the **secondary** callback with **button=right**.\n2. The state's `onTertiaryTapDown` and `onTertiaryTapUp` methods (lines 147–152) are therefore **dead** — they're declared but never invoked anywhere.\n3. `onTertiaryTapUp` at line 152 has a copy-paste error: `TerminalMouseButton.right` where the function is named tertiary and the comment paragraph clearly intends middle. Even if the wiring were correct, the button reported on tertiary-up would be wrong.\n4. The `onTertiaryTapDown` / `onTertiaryTapUp` *parameters* on `TerminalGestureHandler` (lines 24–25, 45–47) and `TerminalGestureDetector` (lines 13–14, 30–31) are **dead public surface** — no in-tree caller passes them, like T-93's `onTapUp`.\n\n**Two layers of \"dead\" again:**\n\n- The wiring is wrong, so middle-clicks dispatch as if they were right-clicks.\n- The public API to receive proper middle-click notifications doesn't reach any caller, so even if a consumer passes `onTertiaryTapDown:` to `TerminalView`, it would do nothing today (TerminalView doesn't expose tertiary callbacks).\n\n**User-visible impact:**\n\nA consumer relying on right-click for context-menu behaviour on `TerminalView` will accidentally trigger that menu on middle-click as well, which is wrong on every desktop platform (middle-click is conventionally paste / scroll-wheel-button). Tests of secondary-tap will pass for both right- and middle-clicks, masking the issue.\n\n**Resolutions, like T-93:**\n\n- **A. Fix the wiring + typo.** In `TerminalGestureHandler.build`, change `onTertiaryTapDown: onSecondaryTapDown` to `onTertiaryTapDown: onTertiaryTapDown` (and same for Up). Fix line 152's `TerminalMouseButton.right` → `middle`. Adds a parameter on `TerminalView` (`onTertiaryTapDown` / `onTertiaryTapUp`) so consumers can register middle-click handlers. Test surfaces the new path.\n- **B. Delete the dead surface.** Remove `onTertiaryTapDown` / `onTertiaryTapUp` parameters from both `TerminalGestureHandler` and `TerminalGestureDetector`, plus the state's tertiary methods. If middle-click ever needs distinct handling, add it back deliberately. Cleanest because no caller depends on tertiary today.\n\nRecommendation: **option B**. Same logic as T-93's recommendation — the surface is documented but unwired and unused. Adding it back later with a real consumer + tests is cheaper than fixing dead surface today.\n\n**Aside:** there's a related dead branch in `onDragStart` (line 176):\n```dart\ndetails.kind == PointerDeviceKind.mouse\n ? renderTerminal.selectCharacters(details.localPosition)\n : renderTerminal.selectWord(details.localPosition);\n```\nThe `PanGestureRecognizer` in `TerminalGestureDetector` is registered with `supportedDevices: {PointerDeviceKind.mouse}` (gesture_detector.dart:143) — so non-mouse drags can't reach `onDragStart`. The `selectWord` else-branch is unreachable through the current wiring. Consider removing it as part of the same cleanup pass, or extending PanGestureRecognizer to accept touch.\n\n**Acceptance:**\n\n- Either:\n - **A:** Tertiary tap parameters on `TerminalView` exist and fire on middle-click with `button=middle`. Regression test verifies a middle click reaches the user callback with the resolved cell offset.\n - **B:** `onTertiaryTapDown` / `onTertiaryTapUp` removed from `TerminalGestureHandler`, `TerminalGestureDetector`, and the state's tertiary methods. Existing tests still pass.\n- The `selectWord` branch in `onDragStart` is either reachable (extend PanGestureRecognizer.supportedDevices) or removed.\n\n**Surfaced from:** T-91 ui/ gesture sweep. The `onTertiaryTap*` lines (gesture_handler.dart:147-152) showed as uncovered in the lcov diff — the gestures sweep skipped writing tests for them because they're unreachable production code.\n\n**Cross-references:** T-91 (epic parent), T-93 (same shape on `onTapUp`), T-89 (coverage epic).\n",
|
||
"status": "backlog",
|
||
"priority": "medium",
|
||
"created_at": "2026-05-08 10:48:59",
|
||
"updated_at": "2026-05-08 11:01:01"
|
||
}
|
||
],
|
||
"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-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-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-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-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": "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"
|
||
},
|
||
{
|
||
"ticket_id": "T-1",
|
||
"field": "parent_id",
|
||
"new_value": "T-6",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-10",
|
||
"field": "parent_id",
|
||
"new_value": "T-2",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-11",
|
||
"field": "parent_id",
|
||
"new_value": "T-6",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-12",
|
||
"field": "parent_id",
|
||
"new_value": "T-2",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-13",
|
||
"field": "parent_id",
|
||
"new_value": "T-6",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-14",
|
||
"field": "parent_id",
|
||
"new_value": "T-6",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-15",
|
||
"field": "parent_id",
|
||
"new_value": "T-6",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-16",
|
||
"field": "parent_id",
|
||
"new_value": "T-6",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-17",
|
||
"field": "parent_id",
|
||
"new_value": "T-8",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-19",
|
||
"field": "parent_id",
|
||
"new_value": "T-2",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-20",
|
||
"field": "parent_id",
|
||
"new_value": "T-2",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-21",
|
||
"field": "parent_id",
|
||
"new_value": "T-4",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-22",
|
||
"field": "parent_id",
|
||
"new_value": "T-4",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-23",
|
||
"field": "parent_id",
|
||
"new_value": "T-4",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-24",
|
||
"field": "parent_id",
|
||
"new_value": "T-3",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-25",
|
||
"field": "parent_id",
|
||
"new_value": "T-8",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-26",
|
||
"field": "parent_id",
|
||
"new_value": "T-8",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-27",
|
||
"field": "parent_id",
|
||
"new_value": "T-8",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-28",
|
||
"field": "parent_id",
|
||
"new_value": "T-8",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-29",
|
||
"field": "parent_id",
|
||
"new_value": "T-4",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-35",
|
||
"field": "parent_id",
|
||
"new_value": "T-4",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-36",
|
||
"field": "parent_id",
|
||
"new_value": "T-7",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-37",
|
||
"field": "parent_id",
|
||
"new_value": "T-6",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-38",
|
||
"field": "parent_id",
|
||
"new_value": "T-6",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-39",
|
||
"field": "parent_id",
|
||
"new_value": "T-6",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-40",
|
||
"field": "parent_id",
|
||
"new_value": "T-8",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-41",
|
||
"field": "parent_id",
|
||
"new_value": "T-7",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-42",
|
||
"field": "parent_id",
|
||
"new_value": "T-5",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-43",
|
||
"field": "parent_id",
|
||
"new_value": "T-3",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-9",
|
||
"field": "parent_id",
|
||
"new_value": "T-2",
|
||
"changed_at": "2026-04-23 10:23:49"
|
||
},
|
||
{
|
||
"ticket_id": "T-17",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "ready",
|
||
"changed_at": "2026-04-24 07:01:08"
|
||
},
|
||
{
|
||
"ticket_id": "T-18",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "ready",
|
||
"changed_at": "2026-04-24 07:01:08"
|
||
},
|
||
{
|
||
"ticket_id": "T-17",
|
||
"field": "status",
|
||
"old_value": "ready",
|
||
"new_value": "backlog",
|
||
"changed_at": "2026-04-24 07:01:48"
|
||
},
|
||
{
|
||
"ticket_id": "T-18",
|
||
"field": "status",
|
||
"old_value": "ready",
|
||
"new_value": "backlog",
|
||
"changed_at": "2026-04-24 07:01:48"
|
||
},
|
||
{
|
||
"ticket_id": "T-63",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "ready",
|
||
"changed_at": "2026-04-24 07:05:40"
|
||
},
|
||
{
|
||
"ticket_id": "T-63",
|
||
"field": "status",
|
||
"old_value": "ready",
|
||
"new_value": "backlog",
|
||
"changed_at": "2026-04-24 07:05:50"
|
||
},
|
||
{
|
||
"ticket_id": "T-63",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "ready",
|
||
"changed_at": "2026-04-24 07:05:51"
|
||
},
|
||
{
|
||
"ticket_id": "T-63",
|
||
"field": "status",
|
||
"old_value": "ready",
|
||
"new_value": "backlog",
|
||
"changed_at": "2026-04-24 07:06:30"
|
||
},
|
||
{
|
||
"ticket_id": "T-63",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "ready",
|
||
"changed_at": "2026-04-24 07:06:32"
|
||
},
|
||
{
|
||
"ticket_id": "T-17",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "ready",
|
||
"changed_at": "2026-04-24 07:07:55"
|
||
},
|
||
{
|
||
"ticket_id": "T-17",
|
||
"field": "status",
|
||
"old_value": "ready",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-04-24 07:07:55"
|
||
},
|
||
{
|
||
"ticket_id": "T-17",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "review",
|
||
"changed_at": "2026-04-24 07:07:55"
|
||
},
|
||
{
|
||
"ticket_id": "T-17",
|
||
"field": "status",
|
||
"old_value": "review",
|
||
"new_value": "done",
|
||
"changed_at": "2026-04-24 07:07:55"
|
||
},
|
||
{
|
||
"ticket_id": "T-17",
|
||
"field": "status",
|
||
"old_value": "done",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-04-24 07:08:00"
|
||
},
|
||
{
|
||
"ticket_id": "T-17",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "done",
|
||
"changed_at": "2026-04-24 07:08:29"
|
||
},
|
||
{
|
||
"ticket_id": "T-17",
|
||
"field": "status",
|
||
"old_value": "done",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-04-24 07:08:29"
|
||
},
|
||
{
|
||
"ticket_id": "T-17",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "ready",
|
||
"changed_at": "2026-04-24 07:08:35"
|
||
},
|
||
{
|
||
"ticket_id": "T-17",
|
||
"field": "status",
|
||
"old_value": "ready",
|
||
"new_value": "backlog",
|
||
"changed_at": "2026-04-24 07:08:35"
|
||
},
|
||
{
|
||
"ticket_id": "T-18",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "ready",
|
||
"changed_at": "2026-04-24 07:08:59"
|
||
},
|
||
{
|
||
"ticket_id": "T-18",
|
||
"field": "status",
|
||
"old_value": "ready",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-04-24 07:08:59"
|
||
},
|
||
{
|
||
"ticket_id": "T-18",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "ready",
|
||
"changed_at": "2026-04-24 07:08:59"
|
||
},
|
||
{
|
||
"ticket_id": "T-18",
|
||
"field": "status",
|
||
"old_value": "ready",
|
||
"new_value": "backlog",
|
||
"changed_at": "2026-04-24 07:08:59"
|
||
},
|
||
{
|
||
"ticket_id": "T-18",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "cancelled",
|
||
"changed_at": "2026-04-24 07:08:59"
|
||
},
|
||
{
|
||
"ticket_id": "T-18",
|
||
"field": "status",
|
||
"old_value": "cancelled",
|
||
"new_value": "backlog",
|
||
"changed_at": "2026-04-24 07:08:59"
|
||
},
|
||
{
|
||
"ticket_id": "T-18",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "done",
|
||
"changed_at": "2026-04-24 07:30:15"
|
||
},
|
||
{
|
||
"ticket_id": "T-18",
|
||
"field": "status",
|
||
"old_value": "done",
|
||
"new_value": "backlog",
|
||
"changed_at": "2026-04-24 07:30:22"
|
||
},
|
||
{
|
||
"ticket_id": "T-63",
|
||
"field": "status",
|
||
"old_value": "ready",
|
||
"new_value": "backlog",
|
||
"changed_at": "2026-04-24 08:08:16"
|
||
},
|
||
{
|
||
"ticket_id": "T-63",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "ready",
|
||
"changed_at": "2026-04-24 08:08:16"
|
||
},
|
||
{
|
||
"ticket_id": "T-63",
|
||
"field": "status",
|
||
"old_value": "ready",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-04-24 08:08:23"
|
||
},
|
||
{
|
||
"ticket_id": "T-63",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "ready",
|
||
"changed_at": "2026-04-24 08:08:38"
|
||
},
|
||
{
|
||
"ticket_id": "T-63",
|
||
"field": "status",
|
||
"old_value": "ready",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-04-24 08:08:40"
|
||
},
|
||
{
|
||
"ticket_id": "T-63",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "review",
|
||
"changed_at": "2026-04-24 08:08:44"
|
||
},
|
||
{
|
||
"ticket_id": "T-63",
|
||
"field": "status",
|
||
"old_value": "review",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-04-24 08:08:48"
|
||
},
|
||
{
|
||
"ticket_id": "T-63",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "ready",
|
||
"changed_at": "2026-04-24 08:16:15"
|
||
},
|
||
{
|
||
"ticket_id": "T-63",
|
||
"field": "status",
|
||
"old_value": "ready",
|
||
"new_value": "backlog",
|
||
"changed_at": "2026-04-24 08:16:19"
|
||
},
|
||
{
|
||
"ticket_id": "T-63",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "ready",
|
||
"changed_at": "2026-04-24 08:16:21"
|
||
},
|
||
{
|
||
"ticket_id": "T-63",
|
||
"field": "status",
|
||
"old_value": "ready",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-04-24 08:16:47"
|
||
},
|
||
{
|
||
"ticket_id": "T-28",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-04-24 08:16:56"
|
||
},
|
||
{
|
||
"ticket_id": "T-43",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "ready",
|
||
"changed_at": "2026-04-24 08:17:11"
|
||
},
|
||
{
|
||
"ticket_id": "T-43",
|
||
"field": "status",
|
||
"old_value": "ready",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-03 20:33:10"
|
||
},
|
||
{
|
||
"ticket_id": "T-49",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-03 20:33:10"
|
||
},
|
||
{
|
||
"ticket_id": "T-61",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-03 20:33:10"
|
||
},
|
||
{
|
||
"ticket_id": "T-62",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-03 20:33:10"
|
||
},
|
||
{
|
||
"ticket_id": "T-63",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-03 20:33:10"
|
||
},
|
||
{
|
||
"ticket_id": "T-21",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "ready",
|
||
"changed_at": "2026-05-03 20:34:52"
|
||
},
|
||
{
|
||
"ticket_id": "T-24",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "ready",
|
||
"changed_at": "2026-05-03 20:36:31"
|
||
},
|
||
{
|
||
"ticket_id": "T-17",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "ready",
|
||
"changed_at": "2026-05-03 20:37:03"
|
||
},
|
||
{
|
||
"ticket_id": "T-18",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "ready",
|
||
"changed_at": "2026-05-03 20:37:30"
|
||
},
|
||
{
|
||
"ticket_id": "T-28",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-03 20:40:22"
|
||
},
|
||
{
|
||
"ticket_id": "T-43",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-05 12:51:24"
|
||
},
|
||
{
|
||
"ticket_id": "T-68",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-05 12:53:11"
|
||
},
|
||
{
|
||
"ticket_id": "T-69",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-05 12:53:11"
|
||
},
|
||
{
|
||
"ticket_id": "T-70",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-05 12:53:11"
|
||
},
|
||
{
|
||
"ticket_id": "T-71",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-05 12:53:11"
|
||
},
|
||
{
|
||
"ticket_id": "T-72",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-05 12:53:11"
|
||
},
|
||
{
|
||
"ticket_id": "T-68",
|
||
"field": "status",
|
||
"old_value": "done",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-05 12:53:15"
|
||
},
|
||
{
|
||
"ticket_id": "T-69",
|
||
"field": "status",
|
||
"old_value": "done",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-05 12:53:15"
|
||
},
|
||
{
|
||
"ticket_id": "T-70",
|
||
"field": "status",
|
||
"old_value": "done",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-05 12:53:15"
|
||
},
|
||
{
|
||
"ticket_id": "T-71",
|
||
"field": "status",
|
||
"old_value": "done",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-05 12:53:15"
|
||
},
|
||
{
|
||
"ticket_id": "T-72",
|
||
"field": "status",
|
||
"old_value": "done",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-05 12:53:15"
|
||
},
|
||
{
|
||
"ticket_id": "T-18",
|
||
"field": "status",
|
||
"old_value": "ready",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-05 12:54:39"
|
||
},
|
||
{
|
||
"ticket_id": "T-18",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-05 12:59:05"
|
||
},
|
||
{
|
||
"ticket_id": "T-78",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-05 12:59:50"
|
||
},
|
||
{
|
||
"ticket_id": "T-78",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-05 13:02:34"
|
||
},
|
||
{
|
||
"ticket_id": "T-75",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-05 13:02:40"
|
||
},
|
||
{
|
||
"ticket_id": "T-75",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-05 13:07:29"
|
||
},
|
||
{
|
||
"ticket_id": "T-76",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-05 13:07:34"
|
||
},
|
||
{
|
||
"ticket_id": "T-76",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-05 13:10:50"
|
||
},
|
||
{
|
||
"ticket_id": "T-77",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-05 13:10:57"
|
||
},
|
||
{
|
||
"ticket_id": "T-77",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-05 13:12:20"
|
||
},
|
||
{
|
||
"ticket_id": "T-79",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-06 07:45:02"
|
||
},
|
||
{
|
||
"ticket_id": "T-79",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-06 07:56:10"
|
||
},
|
||
{
|
||
"ticket_id": "T-21",
|
||
"field": "status",
|
||
"old_value": "ready",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-06 09:17:22"
|
||
},
|
||
{
|
||
"ticket_id": "T-83",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-06 09:17:50"
|
||
},
|
||
{
|
||
"ticket_id": "T-83",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-06 10:12:48"
|
||
},
|
||
{
|
||
"ticket_id": "T-84",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-06 10:17:57"
|
||
},
|
||
{
|
||
"ticket_id": "T-84",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-06 12:17:27"
|
||
},
|
||
{
|
||
"ticket_id": "T-24",
|
||
"field": "status",
|
||
"old_value": "ready",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-06 12:20:58"
|
||
},
|
||
{
|
||
"ticket_id": "T-24",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-06 13:21:15"
|
||
},
|
||
{
|
||
"ticket_id": "T-85",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-06 13:29:05"
|
||
},
|
||
{
|
||
"ticket_id": "T-85",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-06 13:38:38"
|
||
},
|
||
{
|
||
"ticket_id": "T-86",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-06 13:38:44"
|
||
},
|
||
{
|
||
"ticket_id": "T-86",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-06 13:45:12"
|
||
},
|
||
{
|
||
"ticket_id": "T-87",
|
||
"field": "description",
|
||
"new_value": "D-41 lifecycle contract: \"closing a secondary kills that tmux session\" and \"secondary numbering (-1, -2, ...) resets between clide runs since ephemeral state is lost\" — the second clause only holds if non-primary tmux sessions are reaped on app shutdown.\n\nNeither happens today.\n\n**Gap 1 — tab close is UI-only.** `ClaudeSessionHostState` (lib/builtin/claude/src/claude_session_host.dart:18) builds tabs via MultitabController; closing a secondary just removes the entry. No `tmux kill-session` shellout anywhere in lib/builtin/claude/ or lib/src/pty/ (verified by grep for `kill-session` / `killSession` / `tmux kill`).\n\n**Gap 2 — app shutdown leaks secondaries.** Nothing reaps `clide-claude-\u003crepohash\u003e-N` sessions when the app exits. Repro: spawn two secondaries, quit clide, `tmux -L clide list-sessions` still shows them. Confirmed live: 4 sessions on the clide socket (primary + -1, -2, -3) survived prior runs.\n\n**Acceptance:**\n- Closing a secondary tab in the Claude pane runs `tmux -L clide kill-session -t \u003cname\u003e` for that secondary's session name (use existing `secondarySessionName(repoRoot, n)`).\n- App shutdown enumerates `clide-claude-\u003crepohash\u003e-*` sessions on the clide socket and kills the non-primary ones. Primary is left detached (D-41 explicit).\n- `_nextSecondary` index policy: keep the in-process counter for the running app; cold-start always begins at 1 because secondary sessions are gone.\n- Add a test that exercises the close path and asserts the kill-session command was invoked (mock the tmux runner).\n\n**Out of scope:** primary lifecycle (unchanged — survives shutdown by design), general-purpose terminal panes (no tmux wrapping per D-41), Windows behaviour.",
|
||
"changed_at": "2026-05-06 13:46:26"
|
||
},
|
||
{
|
||
"ticket_id": "T-87",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-06 13:46:29"
|
||
},
|
||
{
|
||
"ticket_id": "T-17",
|
||
"field": "description",
|
||
"new_value": "Wire `dart doc` into the CI pipeline so the public API surface (everything in lib/*.dart barrel exports) renders to HTML on every push to main.\n\n**Acceptance:**\n- New CI step in `.gitea/workflows/test.yml` runs `dart doc lib/` and uploads the generated `doc/api/` artefact.\n- Documentation comments on public APIs are validated (no broken references); `dart doc --validate-links` part of the run.\n- Generated docs published somewhere browsable — Gitea Pages, GitHub Pages once the repo moves, or an S3-style static host (decision deferred until Gitea actions land per D-32).\n\n**Not in scope:** writing missing docstrings (separate sweep), private-API docs, package.json-style metadata for pub.dev (we never publish to pub.dev — single binary).",
|
||
"changed_at": "2026-05-06 15:08:22"
|
||
},
|
||
{
|
||
"ticket_id": "T-7",
|
||
"field": "description",
|
||
"new_value": "Tier 5 of the build plan: bring the canvas pane (Obsidian-style spatial whiteboard) and the graph view (force-directed view of vault links) to feature parity.\n\n**Canvas pane (`builtin.canvas`):**\n- Render `.canvas` files (Obsidian-compatible JSON) — nodes (note, text, group, image), edges, layout state.\n- Pan/zoom via `InteractiveViewer`; node selection, drag, resize.\n- Edit affordances: add note from file picker, add text node, draw edge between nodes.\n- Persist layout back to the `.canvas` file on disk.\n\n**Graph view (`builtin.graph`):**\n- Force-directed layout of the current vault (or a filtered subset) — nodes are notes, edges are wikilinks.\n- Hover highlights connected subgraph; click opens the note in the editor.\n- Filter pane: tag include/exclude, file glob, depth-from-active.\n- pql provides the link data (`pql backlinks` / `pql outlinks`); rendering owned in-app.\n\nBoth panes use the existing `MultitabPane` (T-83) for tab management once they ship. Each lives in its own slot per D-47 (canvas in workspace, graph in context panel).",
|
||
"changed_at": "2026-05-06 15:08:37"
|
||
},
|
||
{
|
||
"ticket_id": "T-8",
|
||
"field": "description",
|
||
"new_value": "Tier 6 of the build plan: the things that make clide a real product instead of a working prototype.\n\n**Extension API (third-party Lua):**\n- The Lua runtime supporter tool (D-19) lands as a peer of pql/ptyc.\n- Manifest schema, capability gating, sandboxed FS/IPC access.\n- Same TabContribution / CommandContribution / etc. surface as built-in Dart extensions (D-15).\n- Marketplace / distribution story is OUT OF SCOPE for Tier 6 — local-install only.\n\n**Settings UI (`builtin.settings-ui`):**\n- Schema-driven settings panel reading from the kernel SettingsStore.\n- Render strategy: form fields keyed off the schema each subsystem registers.\n- Edits write back to `.clide/settings.yaml`.\n\n**Theming UI (`builtin.theme-picker` extends):**\n- Live preview of the four bundled themes (D-44).\n- Custom theme: import YAML, validate against schema, register at runtime.\n- Per-component override surface (long horizon).\n\n**Distributable builds:**\n- AppImage / Flatpak for Linux, .dmg for macOS — see T-46.\n- Self-update mechanism — see T-47.\n- License manifest auto-regen as part of the release build.\n\nBig epic — children land incrementally. Most concrete child tickets already exist; this is the umbrella.",
|
||
"changed_at": "2026-05-06 15:08:53"
|
||
},
|
||
{
|
||
"ticket_id": "T-23",
|
||
"field": "description",
|
||
"new_value": "D-48 names `⌘P` (fuzzy file open) and `⌘⇧P` (command palette) as the canonical keyboard navigation. The command palette overlay/widget exists; the keybinding is not yet wired.\n\n**Acceptance:**\n- `⌘⇧P` (`Ctrl+Shift+P` on Linux, follows the kernel keymap normalization) opens the command palette overlay over the active workspace.\n- Esc dismisses; Enter runs the highlighted command; arrow keys move the highlight.\n- Commands listed are everything registered via `CommandContribution` across all activated extensions.\n- Fuzzy match against command title; recent / pinned commands float to the top.\n\n**Implementation hints:**\n- Slot exists: `Slots.commandPalette` is reserved (lib/kernel/src/panels/slot_id.dart).\n- Keybinding goes in `lib/kernel/src/commands/keybindings.dart` per the D-54 keymap.\n- The overlay should not shift layout (D-48 chrome budget — no layout shift on palette open).",
|
||
"changed_at": "2026-05-06 15:09:05"
|
||
},
|
||
{
|
||
"ticket_id": "T-73",
|
||
"field": "description",
|
||
"new_value": "Workaround in T-70 / `lib/src/terminal/src/ui/painter.dart` suppresses bold rendering entirely because Flutter`s synthetic bold drifts glyph advance widths and breaks the monospace cell grid. We lose semantic bold (bash prompts, emphasis in TUIs) as a side effect.\n\n**Acceptance:**\n- A bold-weight monospace face ships alongside the regular weight, with verified-equal advance widths at clide`s render size (cell-grid stable on bold).\n- The painter`s `bold: false` override is reverted; bold attributes from terminal escapes render visually bold.\n- Test: render a known-mixed-weight buffer and assert paragraph maxIntrinsicWidth matches the regular-weight measurement (cell drift = 0).\n\n**Options to evaluate:**\n- Re-add JetBrainsMono-Bold.ttf once we`ve verified its advance widths actually equal the Regular file at our cellSize. (We dropped it because the synthetic-bold issue obscured whether the bundled Bold actually matched.)\n- Switch to a variable JetBrainsMono and request a specific weight axis position.\n- Pick a different mono font whose Bold has guaranteed-equal advance (Berkeley Mono, IBM Plex Mono).\n\nLink-back: T-70 is the workaround; this is the proper fix.",
|
||
"changed_at": "2026-05-06 15:09:20"
|
||
},
|
||
{
|
||
"ticket_id": "T-74",
|
||
"field": "description",
|
||
"new_value": "Mouse wheel scroll is currently translated to PgUp/PgDown key input in `lib/src/terminal/src/terminal_view.dart` as a pragmatic workaround — TUIs that capture mouse events without binding the wheel get a no-op otherwise. The downside: any TUI that does its own wheel handling (selection, click-to-position) loses the actual mouse coordinates because all we send is keyboard.\n\n**Acceptance:**\n- The terminal forwards `PointerScrollEvent` and click/drag/release as proper xterm mouse-protocol escape sequences (CSI `M` / SGR variant per the report mode the inner program declared via `?1000h` / `?1006h`).\n- Tested with `vim` (mouse mode `a`), `htop`, `less` — all should respond to mouse selection / click correctly.\n- Falls back to PgUp/PgDown only when the inner program has not declared a mouse mode.\n- The `mouseInput` API on `Terminal` is wired through `Listener.onPointerSignal` instead of being bypassed.\n\n**Why this isn`t the default already:** the bypass was the cheapest fix to get scroll working at all when the multitab/Scrollable ate scroll events. With those fixes landed, the right-shaped mouse forwarding is the proper next step.",
|
||
"changed_at": "2026-05-06 15:09:36"
|
||
},
|
||
{
|
||
"ticket_id": "T-80",
|
||
"field": "description",
|
||
"new_value": "From the PTY/IPC error-handling audit (T-18, see docs/audits/pty-ipc-error-handling-2026-05-05.md). Two cleanup items rolled together:\n\n**Errno constants (audit item #23):**\n- Magic numbers (`4` for EINTR, `9` for EBADF, `28` for SIGWINCH, `1` for SIGHUP, `32` for EPIPE) appear inline across `lib/src/pty/session.dart` and `lib/src/pty/native_pty.dart`.\n- Centralize them in `lib/src/pty/errors.dart` or a sibling `posix.dart` as named constants.\n- Existing `lib/src/ipc/errno_mapping.dart` already has a `PosixErrno` class — extend it or move to a shared location both layers import from.\n\n**Logger standardization (audit item #22, partial #26):**\n- `lib/src/ipc/server.dart` uses `stderr.writeln(...)` directly; the rest of the daemon either uses no logger or a custom one.\n- The Flutter-host process often consumes stderr, so log lines disappear silently.\n- Pick one logger interface (kernel `log` already exists for the app side), wire `DaemonServer` and the daemon-side handlers to use it.\n- Dispatch error messages should prefix with the request `cmd` so log correlation works (audit item #26).\n\n**Out of scope for this ticket:** changes to log-LEVEL policy, log retention, log files vs stderr — pure substitution job.",
|
||
"changed_at": "2026-05-06 15:09:50"
|
||
},
|
||
{
|
||
"ticket_id": "T-81",
|
||
"field": "description",
|
||
"new_value": "Catch-all for the medium-priority items from the PTY/IPC error-handling audit (T-18, see docs/audits/pty-ipc-error-handling-2026-05-05.md) that didn`t earn dedicated tickets:\n\n- **#17** — `files.read` `readAsStringSync` is unguarded; UTF-8 errors / permissions / mid-read deletion become 500-style dispatch errors. Wrap in try/catch and emit a clean `IpcResponse.err`.\n- **#19** — `PtySession.close` swallows the 500ms timeout silently (`onTimeout: () {}`). Log when the timeout fires so we know SIGKILL was needed.\n- **#20** — Reader isolate treats every negative `read()` return that isn`t EINTR as EOF. Distinguish EBADF/EIO (real EOF) from transient EAGAIN (recoverable) and log the latter.\n- **#21** — `scm_rights.dart` reads cmsg-data fd without verifying `dataOffset + 4 \u003c= msgControllen`. Bounds check before deref so a malformed peer can`t feed garbage as an fd.\n- **#25** — `_gitError` in `lib/src/daemon/git_commands.dart` always reports `tool_error`; push rejections / merge conflicts should map to `IpcExitCode.conflict` when stderr matches known patterns.\n- **#27** — `pane.spawn` returns `ok` even when `registry.write(id, bytes)` returned `n == -1`. Distinguish the failure.\n- **#28** — `IpcResponse.fromJson` throws `TypeError` on a malformed peer response missing `error`. Graceful degrade.\n- **#29** — PATH resolution in `native_pty.dart` uses the first existing match without `X_OK` check; non-executable files shadow valid binaries further along PATH.\n\nLand each as a small focused commit; ticket closes when all items above are merged.",
|
||
"changed_at": "2026-05-06 15:10:09"
|
||
},
|
||
{
|
||
"ticket_id": "T-82",
|
||
"field": "description",
|
||
"new_value": "User request: ship a Catppuccin theme alongside the four bundled themes (clide, midnight, paper, terminal — D-44).\n\n**Acceptance:**\n- New theme YAML at `lib/kernel/src/theme/themes/catppuccin.yaml` (or split for the four Catppuccin flavours: latte / frappe / macchiato / mocha — pick one or all four; recommend Mocha as the dark default and Latte for the light variant).\n- Tokens populated for every key in `SurfaceTokens` (lib/kernel/src/theme/tokens.dart) — chrome, panels, tabs, list items, status colours, syntax tokens (D-45).\n- WCAG-AA contrast gate (D-22) passes against the theme.\n- Listed in `assets/licenses.yaml` with the upstream license (MIT) and attribution.\n- Theme picker (`builtin.theme-picker`) shows it in the live-switch menu.\n\n**Source palettes:** https://github.com/catppuccin/catppuccin#-palette — official hex values, don`t paraphrase.\n\n**Why now:** test user request; also a good exercise for any future user-contributed theme since Catppuccin is the most common one to ask for.",
|
||
"changed_at": "2026-05-06 15:10:22"
|
||
},
|
||
{
|
||
"ticket_id": "T-88",
|
||
"field": "description",
|
||
"new_value": "D-59 (bundled git via dugite-native) costs out: \"Must track dugite-native releases for security updates.\" Up to today this lived only in the D-record prose with no tracking — clean-house v1 flagged it as RULE-SUNSET-WITHOUT-TICKET.\n\n**Acceptance:**\n- A periodic check (manual or scheduled) compares the bundled `native/dugite/` SHA against the latest release at https://github.com/desktop/dugite-native/releases.\n- When a new release lands with a CVE label or fixes a CVE-tagged git release, schedule a bump; otherwise track the version drift.\n- Bumps follow D-63: rebuild with the new SHA, update `BUILD.md`, update `assets/licenses.yaml` if the dugite or git version changed, refresh the binary in `native/dugite/`.\n\n**Periodicity:** quarterly check is enough during normal operation; faster on git CVE announcements (subscribe to https://github.com/git/git/security/advisories, https://github.com/desktop/dugite-native/security/advisories).\n\n**Out of scope:** the rebuild itself is T-25 (CI reproducible build for libtree-sitter.so) — same shape applies to dugite. This ticket is the calendar; T-25 is the machine.",
|
||
"changed_at": "2026-05-06 15:10:35"
|
||
},
|
||
{
|
||
"ticket_id": "T-17",
|
||
"field": "status",
|
||
"old_value": "ready",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-06 15:39:53"
|
||
},
|
||
{
|
||
"ticket_id": "T-73",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "ready",
|
||
"changed_at": "2026-05-06 15:39:53"
|
||
},
|
||
{
|
||
"ticket_id": "T-87",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-06 15:39:53"
|
||
},
|
||
{
|
||
"ticket_id": "T-17",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-06 15:45:51"
|
||
},
|
||
{
|
||
"ticket_id": "T-17",
|
||
"field": "status",
|
||
"old_value": "done",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-06 15:51:26"
|
||
},
|
||
{
|
||
"ticket_id": "T-17",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-06 15:58:30"
|
||
},
|
||
{
|
||
"ticket_id": "T-73",
|
||
"field": "description",
|
||
"old_value": "Workaround in T-70 / `lib/src/terminal/src/ui/painter.dart` suppresses bold rendering entirely because Flutter`s synthetic bold drifts glyph advance widths and breaks the monospace cell grid. We lose semantic bold (bash prompts, emphasis in TUIs) as a side effect.\n\n**Acceptance:**\n- A bold-weight monospace face ships alongside the regular weight, with verified-equal advance widths at clide`s render size (cell-grid stable on bold).\n- The painter`s `bold: false` override is reverted; bold attributes from terminal escapes render visually bold.\n- Test: render a known-mixed-weight buffer and assert paragraph maxIntrinsicWidth matches the regular-weight measurement (cell drift = 0).\n\n**Options to evaluate:**\n- Re-add JetBrainsMono-Bold.ttf once we`ve verified its advance widths actually equal the Regular file at our cellSize. (We dropped it because the synthetic-bold issue obscured whether the bundled Bold actually matched.)\n- Switch to a variable JetBrainsMono and request a specific weight axis position.\n- Pick a different mono font whose Bold has guaranteed-equal advance (Berkeley Mono, IBM Plex Mono).\n\nLink-back: T-70 is the workaround; this is the proper fix.",
|
||
"new_value": "Workaround in T-70 / `lib/src/terminal/src/ui/painter.dart` suppresses bold rendering entirely because Flutter`s synthetic bold drifts glyph advance widths and breaks the monospace cell grid. We lose semantic bold (bash prompts, emphasis in TUIs) as a side effect.\n\n**Acceptance:**\n- A bold-weight monospace face ships alongside the regular weight, with verified-equal advance widths at clide`s render size (cell-grid stable on bold).\n- The painter`s `bold: false` override is reverted; bold attributes from terminal escapes render visually bold.\n- Test: render a known-mixed-weight buffer and assert paragraph maxIntrinsicWidth matches the regular-weight measurement (cell drift = 0).\n\n**Options to evaluate:**\n- Re-add JetBrainsMono-Bold.ttf once we`ve verified its advance widths actually equal the Regular file at our cellSize. (We dropped it because the synthetic-bold issue obscured whether the bundled Bold actually matched.)\n- Switch to a variable JetBrainsMono and request a specific weight axis position.\n- Pick a different mono font whose Bold has guaranteed-equal advance (Berkeley Mono, IBM Plex Mono).\n\n---\nRefinement (2026-05-06):\n\nInvestigation showed the perceived over-bolding in the Claude pane was synthetic-bold smearing (Flutter overpaints when no Bold.ttf is registered), not aggressive-but-legitimate bold. Visual A/B confirms a real bold face renders crisp emphasis without smearing. No per-pane renderer config needed.\n\n**Concrete fix shape:**\n1. `pubspec.yaml` (family JetBrainsMono): register `JetBrainsMono-Bold.ttf` with `weight: 700` and `JetBrainsMono-BoldItalic.ttf` with `weight: 700, style: italic`. Files are already on disk under `assets/fonts/jetbrains_mono/`.\n2. `assets/licenses.yaml` JetBrainsMono entry: update `weights_bundled` to `[Regular, Italic, Bold, BoldItalic]` per D-42 (must match what is actually wired into the font family).\n3. `lib/src/terminal/src/ui/painter.dart:198`: revert the `bold: false` override and delete the now-stale comment block at lines 193-195.\n4. Test: render a mixed-weight buffer and assert `paragraph.maxIntrinsicWidth` matches the all-regular measurement (cell drift = 0). If parity fails, the fallback options stay open (variable JetBrainsMono / Berkeley Mono / IBM Plex Mono).\n\nWorkaround comment (`Skip bold rendering — Flutter`s synthetic bold...`) goes away in step 3.",
|
||
"changed_at": "2026-05-06 16:13:35"
|
||
},
|
||
{
|
||
"ticket_id": "T-73",
|
||
"field": "status",
|
||
"old_value": "ready",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-06 16:13:39"
|
||
},
|
||
{
|
||
"ticket_id": "T-74",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-06 16:13:39"
|
||
},
|
||
{
|
||
"ticket_id": "T-73",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-06 20:29:28"
|
||
},
|
||
{
|
||
"ticket_id": "T-89",
|
||
"field": "description",
|
||
"new_value": "Today: total line coverage **34.9%** (2984/8549 lines). Per area:\n\n| area | hit / total | % |\n|---|---|---|\n| `lib/src/terminal/` | 12 / 3167 | **0.4%** |\n| `lib/widgets/` | 537 / 1184 | 45.4% |\n| `lib/src/pql/` | 45 / 94 | 47.9% |\n| `lib/builtin/` | 269 / 550 | 48.9% |\n| `lib/kernel/` | 889 / 1691 | 52.6% |\n| `lib/src/daemon/` | 320 / 584 | 54.8% |\n| `lib/src/pty/` | 130 / 216 | 60.2% |\n| `lib/src/git/` | 356 / 518 | 68.7% |\n| `lib/src/ipc/` | 110 / 156 | 70.5% |\n| `lib/src/files/` | 98 / 133 | 73.7% |\n| `lib/extension/` | 41 / 54 | 75.9% |\n| `lib/src/editor/` | 107 / 129 | 82.9% |\n| `lib/src/panes/` | 70 / 73 | 95.9% |\n\nThe dominant hole is `lib/src/terminal/` — the terminal emulator code. Header still credits xterm.dart upstream, but per CLAUDE.md (\"Own the rendering stack\") and the explicit guidance attached to this epic: in-tree code is owned, no carve-outs, same coverage bar.\n\n**End state:**\n- Total line coverage ≥ 95% (no per-area carve-outs).\n- Pre-push gate hard-fails on coverage drops via a committed floor (`coverage/floor.txt`); floor ratchets up only.\n- Per D-66 the gate lands at the *current* floor in the first child ticket; subsequent floor bumps come with the test-writing children.\n\n**Acceptance criteria for closing this epic:**\n1. `coverage/floor.txt` reads `95` (or higher).\n2. `flutter test --coverage --exclude-tags forkpty` produces ≥95% line coverage.\n3. The pre-push gate computes coverage and rejects pushes below the floor.\n4. No new `// ignore:` / `// ignore_for_file:` / analysis-options excludes were added to dodge the gate. Any suppression added during this campaign needs explicit approval and an inline reason at the suppression site.\n\n**Strategy / child shape:**\nThe gate plumbing lands first so future test additions are visible as floor bumps. Then per-area sweeps — biggest holes first because they move the global number fastest, but each area is its own child ticket with its own scope.\n\n1. **Gate plumbing** (first child) — install the ratchet, lock in current floor, expose `make coverage-gate` and wire it into `make push-check`.\n2. **`lib/src/terminal/` sweep** (second child, by far the largest) — likely splits into sub-children once the area's structure is mapped (parser, buffer, painter, gesture handling, mouse reporting, escape sequences, cell-grid measurement, etc.).\n3. **Per-area sweeps** — claim a child ticket when starting an area; close when the area hits 95%. Order by impact: terminal → widgets → pql → builtin → kernel → daemon → pty → git → ipc → files → extension → editor.\n4. **Final ratchet** — once total ≥ 95%, set floor to 95 and close the epic.\n\n**Out of scope:**\n- Branch coverage (lcov data is weak for branch on Dart; line coverage is the contract).\n- Integration / E2E coverage (only widget + unit feed lcov).\n- `lib/test_app.dart` exclusion: verify in the gate child whether Dart's coverage tooling already excludes it; if not, decide explicitly rather than carve it out silently.\n\n**Cross-references:** D-66 (this epic's decision record), D-29 (pre-push gate scope), \"Own the rendering stack\" guardrail (CLAUDE.md).\n",
|
||
"changed_at": "2026-05-06 20:37:40"
|
||
},
|
||
{
|
||
"ticket_id": "T-90",
|
||
"field": "parent_id",
|
||
"new_value": "T-89",
|
||
"changed_at": "2026-05-06 20:38:24"
|
||
},
|
||
{
|
||
"ticket_id": "T-90",
|
||
"field": "description",
|
||
"new_value": "First child of T-89. Locks in the no-regression contract before any test-writing children land — so subsequent floor bumps are explicit and reviewable.\n\n**What lands:**\n\n1. **`coverage/floor.txt`** — committed file containing today's actual line-coverage number (currently `34` after rounding down from 34.9% to give a small margin against measurement jitter; the gate child re-measures on first install and writes the exact number it observes minus 1 if needed).\n2. **`ci/coverage_gate.sh`** — runs `flutter test --coverage --exclude-tags forkpty`, parses `coverage/lcov.info` (sum of LH / sum of LF * 100), compares to `coverage/floor.txt`, exits non-zero if below, prints the actual percentage either way.\n3. **`Makefile` `coverage-gate` target** — wraps `ci/coverage_gate.sh` for local use.\n4. **`make push-check`** — adds `coverage-gate` to its dependency list. Pre-push hook (already wired via `.githooks/pre-push`) picks this up automatically.\n5. **`coverage/README.md`** — short note explaining the floor file, how to bump it (rule: bump only in the same commit that adds covering tests, with the bump reflecting the measured improvement), and the link to D-66.\n\n**Acceptance:**\n- `make coverage-gate` passes on a clean checkout (floor matches reality).\n- `make push-check` includes the gate.\n- A test deletion would fail `make coverage-gate` (verify by experiment, not just by reading the script — invariant has to actually hold).\n- Pre-push hook runs the gate (verify by attempting a deliberately-failing push and seeing it blocked).\n- `coverage/lcov.info` is gitignored (it's regenerated on every run; only `floor.txt` is committed).\n\n**Out of scope:**\n- Bumping the floor toward 95% — that's the test-writing children's job.\n- Branch coverage — D-66 explicitly excludes.\n- Per-area floors — D-66 chose a single global floor for simplicity.\n\n**Notes:**\n- `lcov` CLI is not installed on this dev machine; parser must be self-contained (awk or python). Don't introduce `lcov` as a dependency of the gate.\n- `coverage/lcov.info` is the standard Dart output path of `flutter test --coverage`. Don't relocate.\n- `flutter test --coverage` runs all tests including `forkpty`-tagged ones, which fail under the flutter test runner (see comment in `test/pty/session_test.dart`); `--exclude-tags forkpty` matches `ci/test.sh`. Forkpty-tagged tests don't currently feed lcov because they run via `dart test`; check whether their coverage can be merged later (separate ticket if not).\n",
|
||
"changed_at": "2026-05-06 20:39:05"
|
||
},
|
||
{
|
||
"ticket_id": "T-74",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "ready",
|
||
"changed_at": "2026-05-06 20:39:45"
|
||
},
|
||
{
|
||
"ticket_id": "T-89",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-06 20:39:45"
|
||
},
|
||
{
|
||
"ticket_id": "T-90",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-06 20:39:45"
|
||
},
|
||
{
|
||
"ticket_id": "T-74",
|
||
"field": "status",
|
||
"old_value": "ready",
|
||
"new_value": "ready",
|
||
"changed_at": "2026-05-06 20:40:13"
|
||
},
|
||
{
|
||
"ticket_id": "T-89",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-06 20:40:16"
|
||
},
|
||
{
|
||
"ticket_id": "T-90",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-06 20:40:16"
|
||
},
|
||
{
|
||
"ticket_id": "T-90",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-06 20:44:47"
|
||
},
|
||
{
|
||
"ticket_id": "T-91",
|
||
"field": "parent_id",
|
||
"new_value": "T-89",
|
||
"changed_at": "2026-05-06 20:49:47"
|
||
},
|
||
{
|
||
"ticket_id": "T-91",
|
||
"field": "description",
|
||
"new_value": "Second child of T-89. The dominant coverage hole — `lib/src/terminal/` is 12 / 3167 lines (0.4%), about 56% of the global gap.\n\n**Today's distribution within `lib/src/terminal/src/`:**\n\n| sub-area | hit / total | % | shape |\n|---|---|---|---|\n| `core/buffer/` | 0 / 581 | 0.0% | pure data structures |\n| `core/escape/` | 1 / 473 | 0.2% | pure parser logic |\n| `core/input/` | 4 / 292 | 1.4% | key + keytab tables |\n| `core/(root)` | 0 / 188 | 0.0% | callback wiring |\n| `core/mouse/` | 3 / 51 | 5.9% | encoder logic |\n| `ui/` | 3 / 823 | 0.4% | painters + render objects |\n| `(root)` | 0 / 471 | 0.0% | `Terminal` + `TerminalView` |\n| `utils/` | 1 / 251 | 0.4% | circular buffer etc. |\n\n**Strategy — pure logic first, then UI:**\n\n1. **`core/buffer/`** (581 lines, biggest single chunk, all pure Dart) — line/segment/cell models, scrollback math, range queries. Unit tests exercise the data structure directly.\n2. **`core/escape/`** (473 lines, pure state machine) — feed ANSI/VT byte streams in, assert the resulting buffer/cursor state. Reusable corpus from xterm.js / xterm.dart upstream is fair game.\n3. **`core/input/`** (292 lines) — key + keytab tables; assert mapping from logical key to escape bytes per terminal mode.\n4. **`core/mouse/`** (51 lines) — encoder for SGR-1006 / X11 mouse protocols. Small, fast.\n5. **`utils/`** (251 lines) — circular buffer + helpers; trivial unit tests.\n6. **`(root)`** (`terminal.dart`, `terminal_view.dart`, 471 lines) — orchestration; some unit-testable, some needs widget tests.\n7. **`ui/`** (823 lines) — painters and render objects; widget + golden tests. Hardest, most pixel-sensitive.\n\n**Acceptance:**\n- `lib/src/terminal/` line coverage ≥ 95%.\n- All tests fast (\u003c5 s for the area's test files combined; no real PTY shell-out).\n- Helper fixtures live in `test/terminal/_helpers/` if they get reused across files.\n- Per D-66, no `// ignore:` directives or analysis_options excludes added to dodge coverage.\n- `coverage_floor:` in `pubspec.yaml` is bumped in lockstep — every commit that adds covering tests bumps the floor by the measured delta (rounded down to the integer below the new percentage).\n\n**Strategy on commit cadence:**\nLand tests in batches per sub-area (one commit per `core/buffer/`, `core/escape/`, etc.). Each batch:\n- Adds tests under `test/terminal/\u003csub-area\u003e/`.\n- Re-measures coverage, bumps `coverage_floor:` to the new integer (or as high as the gate currently allows).\n- Documents in CHANGELOG under \"Changed\" (test-only changes can also fold into one CHANGELOG entry per area).\n\n**Out of scope:**\n- Refactoring `lib/src/terminal/` for testability — if a piece is genuinely untestable, surface it (don't silence it). Code under `lib/` is owned regardless of upstream attribution; refactor to make it testable rather than carving it out.\n- Branch coverage (D-66 explicitly excludes).\n- Mouse / keyboard *protocol* round-trips against real TUIs (that's T-74 and integration work).\n\n**Cross-references:** D-66 (coverage gate), T-89 (epic), T-90 (gate plumbing).\n",
|
||
"changed_at": "2026-05-06 20:50:29"
|
||
},
|
||
{
|
||
"ticket_id": "T-91",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-06 20:50:32"
|
||
},
|
||
{
|
||
"ticket_id": "T-92",
|
||
"field": "description",
|
||
"new_value": "**File:** `lib/src/terminal/src/core/reflow.dart` (`_LineReflow._addPart`).\n\n**Bug:** Anchors whose x-coordinate sits past the source line's `trimmedLength` get reparented onto whatever `_builder._result` is active at that moment. After the main reparenting loop has run with `lineFilled` on the final iteration, `_builder.take()` has already moved the filled line into `_lines` and reset `_result` to a fresh empty `BufferLine`. The post-loop block (lines 144-150 in the current source) then reparents the tail anchor onto that fresh empty line. If no further content lands in the builder before `finish()` exits — which is the case whenever the post-loop block fires on a non-wrapped logical line — the empty builder line is never appended to `_lines`, and the anchor ends up pointing to a `BufferLine` that the reflow result never includes.\n\n**Trigger paths in production code:**\n\n1. `SelectAllTextIntent` (`lib/src/terminal/src/ui/shortcut/actions.dart:60-63`) creates the end anchor at `x = terminal.viewWidth`. That is *exactly* the past-trimmedLength position for every partially-filled line in the buffer. Any user who Ctrl+A's and then resizes the terminal narrower hits the bug for every short line in the scrollback.\n2. `selectCharacters` / mouse drag selection (`lib/src/terminal/src/ui/render.dart:286-298`) — anchors are clamped to `[0, viewWidth-1]` but can sit past `trimmedLength` on a partially-filled line. Drag past content end + resize narrower trips the bug.\n3. `selectWord` (`render.dart:264-275`) — `getWordBoundary` walks until a separator OR `viewWidth`, so the end anchor of a near-edge word can land at `x = viewWidth` and trip the bug on resize.\n\n**Symptom:** selection extents lose track of content after a width change. Visible highlight stops moving with the underlying selection, or extents disappear off the rendered buffer entirely. No exception is thrown — the dangling anchor still has a valid `BufferLine` reference, it just isn't on a line in the visible buffer.\n\n**Acceptance:**\n- New unit test in `test/terminal/core/core_test.dart` (or a sibling) that:\n - Constructs a partially-filled source line and an anchor past `trimmedLength`,\n - Runs reflow shrink,\n - Asserts the anchor's owning line is *in the reflow output list*, not dangling.\n- The existing widget-level select-all behaviour still works on resize: a Ctrl+A across the buffer followed by a narrower resize keeps the selection's end anchor pointing to a line in the reflowed result.\n- No regression in the existing reflow tests (the tail-clamp cases, wide-char boundaries, multi-line wrapped runs).\n\n**Investigation notes (do not skip):**\n\nThe fix is conceptually small — the reparented tail anchor must land on a line that gets emitted. Two candidate shapes:\n\n- **Skip the reparent entirely.** Anchors past `trimmedLength` arguably shouldn't survive a shrink that drops their column. Risk: callers expect every anchor on the source to be reparented somewhere; silently disposing them changes semantics.\n- **Force `finish()` to flush the builder line.** Whenever the tail-anchor branch fired during `_addPart`, set a flag, and have `finish()` emit the builder line even if `isEmpty`. Risk: empty trailing lines start showing up in the reflow result, which would break callers that assume reflow returns only content lines (resize loop in `Buffer.resize` pads to viewport height after, so an extra empty might be benign there but worth checking).\n\nDon't pick one without re-reading the full reflow flow + every caller of `reflow()`. The selection round-trip is also tested at the widget layer indirectly — verify `make test-a11y` still passes after the change.\n\n**Surfaced from:** T-91 test sweep (commit 3196c49). The 100% coverage on reflow.dart was achieved by exercising this branch but explicitly *not* asserting on the resulting line being in the output — see the test comment.\n\n**Cross-references:** T-91 (epic parent for the test sweep that found this), T-89 (coverage epic), the test in `test/terminal/core/core_test.dart` group `reflow` named \"anchors on the source line tail (past trimmedLength) get reparented\" documents the current (buggy) behaviour.\n",
|
||
"changed_at": "2026-05-07 05:52:26"
|
||
},
|
||
{
|
||
"ticket_id": "T-92",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-07 05:56:47"
|
||
},
|
||
{
|
||
"ticket_id": "T-92",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-07 05:58:51"
|
||
},
|
||
{
|
||
"ticket_id": "T-93",
|
||
"field": "description",
|
||
"new_value": "**File:** `lib/src/terminal/src/ui/terminal_view.dart` + `gesture/gesture_handler.dart` + `gesture/gesture_detector.dart`.\n\n**Bug:** The `TerminalView.onTapUp` callback parameter (declared at `terminal_view.dart:91`, documented as \"Callback for when the user taps on the terminal\") is wired to a path that is never executed. Internal trace:\n\n1. `TerminalView.build` passes `onTapUp: _onTapUp` to `TerminalGestureHandler` (terminal_view.dart:304).\n2. `TerminalGestureHandler.build` forwards via `onTapUp: widget.onTapUp` to `TerminalGestureDetector` (gesture_handler.dart:67).\n3. `TerminalGestureDetector._handleTapUp` (gesture_detector.dart:84) fires only `widget.onSingleTapUp`, never `widget.onTapUp`. The `onTapUp` field on `TerminalGestureDetector` is declared (gesture_detector.dart:31) but never invoked.\n\nIndependently, `TerminalGestureHandler` exposes an `onSingleTapUp` parameter that *is* properly wired (handler line 139 calls `_tapUp(widget.onSingleTapUp, ...)`), but `TerminalView` never passes a value for it.\n\nNet result: the public `TerminalView.onTapUp` API is silently a no-op. Any caller passing `onTapUp:` to `TerminalView` thinks they registered for tap notifications, but the callback never fires.\n\n**Trigger paths in production:**\n\n`TerminalView.onTapUp` is called by anyone who wants to intercept terminal taps (e.g. for a custom click-to-position cursor, hyperlink detection, click telemetry). Surfaced during T-91's TerminalView widget tests: a test that registered `onTapUp` via `tester.tap` confirmed the callback never fires.\n\n**Acceptance:**\n- `TerminalView.onTapUp` fires on every tap up over the widget (or only confirmed single taps — pick one and document the contract; both shapes are reasonable for terminal selection).\n- `TerminalView.onSingleTapUp` either becomes a public parameter on `TerminalView` (mirroring TerminalGestureHandler), or the docstring on `onTapUp` is clarified to match what actually happens.\n- New widget test in `test/terminal/terminal_view_test.dart` that registers a callback, taps with `tester.tap` + `pumpAndSettle`, and asserts the callback ran with the resolved `CellOffset`.\n- Any pre-existing internal use of `TerminalGestureDetector.onTapUp` vs. `onSingleTapUp` should be reviewed: pick one canonical hook for \"tap completed\" and remove the other, or document the difference.\n\n**Investigation notes:**\n\n- `TerminalGestureDetector` declares both `onTapUp` and `onSingleTapUp` (lines 31 / 33). Only the latter is ever invoked. The `onTapUp` field is therefore dead surface.\n- The cleanest fix shape: have `_handleTapUp` (gesture_detector.dart:84) call `widget.onTapUp` *in addition to* (or instead of) `widget.onSingleTapUp`, depending on the desired contract:\n - **Option A** — fire `onTapUp` on every up event (raw); fire `onSingleTapUp` only on confirmed singles. Most flexible.\n - **Option B** — collapse to a single hook (`onTapUp`) and remove `onSingleTapUp`. Simpler but loses the double-tap distinction.\n- Either option needs verification against the existing single/double-tap timer flow. Don't pick without re-reading how `_isDoubleTap` is used.\n\n**Surfaced from:** T-91 test sweep on `terminal_view.dart`. Documented in the test file as \"skipped — see T-93\".\n\n**Cross-references:** T-91 (epic parent for the test sweep that found this), T-89 (coverage epic), the `onTapUp` test stubbed in `test/terminal/terminal_view_test.dart` will become live once the wiring is fixed.\n",
|
||
"changed_at": "2026-05-07 06:34:22"
|
||
},
|
||
{
|
||
"ticket_id": "T-93",
|
||
"field": "description",
|
||
"old_value": "**File:** `lib/src/terminal/src/ui/terminal_view.dart` + `gesture/gesture_handler.dart` + `gesture/gesture_detector.dart`.\n\n**Bug:** The `TerminalView.onTapUp` callback parameter (declared at `terminal_view.dart:91`, documented as \"Callback for when the user taps on the terminal\") is wired to a path that is never executed. Internal trace:\n\n1. `TerminalView.build` passes `onTapUp: _onTapUp` to `TerminalGestureHandler` (terminal_view.dart:304).\n2. `TerminalGestureHandler.build` forwards via `onTapUp: widget.onTapUp` to `TerminalGestureDetector` (gesture_handler.dart:67).\n3. `TerminalGestureDetector._handleTapUp` (gesture_detector.dart:84) fires only `widget.onSingleTapUp`, never `widget.onTapUp`. The `onTapUp` field on `TerminalGestureDetector` is declared (gesture_detector.dart:31) but never invoked.\n\nIndependently, `TerminalGestureHandler` exposes an `onSingleTapUp` parameter that *is* properly wired (handler line 139 calls `_tapUp(widget.onSingleTapUp, ...)`), but `TerminalView` never passes a value for it.\n\nNet result: the public `TerminalView.onTapUp` API is silently a no-op. Any caller passing `onTapUp:` to `TerminalView` thinks they registered for tap notifications, but the callback never fires.\n\n**Trigger paths in production:**\n\n`TerminalView.onTapUp` is called by anyone who wants to intercept terminal taps (e.g. for a custom click-to-position cursor, hyperlink detection, click telemetry). Surfaced during T-91's TerminalView widget tests: a test that registered `onTapUp` via `tester.tap` confirmed the callback never fires.\n\n**Acceptance:**\n- `TerminalView.onTapUp` fires on every tap up over the widget (or only confirmed single taps — pick one and document the contract; both shapes are reasonable for terminal selection).\n- `TerminalView.onSingleTapUp` either becomes a public parameter on `TerminalView` (mirroring TerminalGestureHandler), or the docstring on `onTapUp` is clarified to match what actually happens.\n- New widget test in `test/terminal/terminal_view_test.dart` that registers a callback, taps with `tester.tap` + `pumpAndSettle`, and asserts the callback ran with the resolved `CellOffset`.\n- Any pre-existing internal use of `TerminalGestureDetector.onTapUp` vs. `onSingleTapUp` should be reviewed: pick one canonical hook for \"tap completed\" and remove the other, or document the difference.\n\n**Investigation notes:**\n\n- `TerminalGestureDetector` declares both `onTapUp` and `onSingleTapUp` (lines 31 / 33). Only the latter is ever invoked. The `onTapUp` field is therefore dead surface.\n- The cleanest fix shape: have `_handleTapUp` (gesture_detector.dart:84) call `widget.onTapUp` *in addition to* (or instead of) `widget.onSingleTapUp`, depending on the desired contract:\n - **Option A** — fire `onTapUp` on every up event (raw); fire `onSingleTapUp` only on confirmed singles. Most flexible.\n - **Option B** — collapse to a single hook (`onTapUp`) and remove `onSingleTapUp`. Simpler but loses the double-tap distinction.\n- Either option needs verification against the existing single/double-tap timer flow. Don't pick without re-reading how `_isDoubleTap` is used.\n\n**Surfaced from:** T-91 test sweep on `terminal_view.dart`. Documented in the test file as \"skipped — see T-93\".\n\n**Cross-references:** T-91 (epic parent for the test sweep that found this), T-89 (coverage epic), the `onTapUp` test stubbed in `test/terminal/terminal_view_test.dart` will become live once the wiring is fixed.\n",
|
||
"new_value": "**File:** `lib/src/terminal/src/terminal_view.dart` (param + wiring) + `lib/src/terminal/src/ui/gesture/gesture_handler.dart` (pass-through) + `lib/src/terminal/src/ui/gesture/gesture_detector.dart` (dead field).\n\n**Bug:** `TerminalView.onTapUp` is documented as \"Callback for when the user taps on the terminal\" but is wired to a code path that is never executed:\n\n1. `TerminalView.build` passes `onTapUp: _onTapUp` to `TerminalGestureHandler` (terminal_view.dart:304).\n2. `TerminalGestureHandler.build` forwards via `onTapUp: widget.onTapUp` to `TerminalGestureDetector` (gesture_handler.dart:67).\n3. `TerminalGestureDetector._handleTapUp` (gesture_detector.dart:84) fires only `widget.onSingleTapUp`, never `widget.onTapUp`. The `onTapUp` field on `TerminalGestureDetector` (declared at line 31) is never invoked anywhere in the file.\n\nThe `_onTapUp` method on `TerminalViewState` itself is also dead — it's only referenced from the dead-field path above.\n\n**Two layers of \"dead\" here:**\n\n- The **wiring** is dead — nothing inside the terminal renderer calls into `widget.onTapUp` after the field is set.\n- The **public API surface** has zero in-tree callers. The single `TerminalView` callsite is `lib/widgets/src/clide_pty_view.dart`, and it doesn't pass any tap callback.\n\nSo no production code depends on this parameter; nothing observable changes regardless of which way it gets resolved. (TerminalView's `onSecondaryTapDown` / `onSecondaryTapUp` are wired correctly — `TapGestureRecognizer.onSecondaryTap*` fires them directly. Only `onTapUp` is bad.)\n\n**Two clean resolutions:**\n\n- **A. Fix the wiring.** Call `widget.onTapUp` from `_handleTapUp` (gesture_detector.dart:84) so the parameter does what its docstring claims. Risk: the existing `onSingleTapUp` semantics (gated on `!_isDoubleTap`) need a clearly-defined relationship to `onTapUp` — pick one of:\n - `onTapUp` fires on every tap up (raw); `onSingleTapUp` fires only on confirmed singles.\n - Collapse the two into a single hook and document the contract.\n- **B. Delete the dead surface.** Remove `onTapUp` from `TerminalView`, `TerminalGestureHandler`, and `TerminalGestureDetector` (plus the `_onTapUp` method on `TerminalViewState`). Cleanest because no caller is broken — the surface is documented but unused. If a future caller wants tap notifications, expose `onSingleTapUp` (which works) or re-add `onTapUp` *with wiring* at that point.\n\nRecommendation: **option B**. The \"remove unused public API\" pattern matches the no-pre-existing-excuse / dead-code cleanups in this campaign (the keytab-token defensive throws, the `_LineBuilder.isEmpty` getter, the `_buildDispatcher` rename). If a feature is needed later, add it back deliberately with tests and a real caller.\n\n**Acceptance:**\n\n- Either:\n - **A:** `TerminalView.onTapUp` fires on every tap up; new widget test in `test/terminal/terminal_view_test.dart` verifies the callback runs with a `CellOffset`. Document the contract on the docstring.\n - **B:** `TerminalView.onTapUp` parameter removed; `_onTapUp` method on `TerminalViewState` removed; `onTapUp` parameter on `TerminalGestureHandler` removed; `onTapUp` field + setter on `TerminalGestureDetector` removed. Existing tests still pass.\n- No regression in secondary-tap behaviour (those parameters are wired correctly via `TapGestureRecognizer.onSecondary*`).\n\n**Surfaced from:** T-91 test sweep on `terminal_view.dart`. The widget test that registered `onTapUp` and tapped via `tester.tap` confirmed the callback never fires regardless of `pumpAndSettle` / gesture flushing.\n\n**Cross-references:** T-91 (epic parent), T-89 (coverage epic).\n",
|
||
"changed_at": "2026-05-07 06:35:32"
|
||
},
|
||
{
|
||
"ticket_id": "T-93",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "in_progress",
|
||
"changed_at": "2026-05-07 06:45:24"
|
||
},
|
||
{
|
||
"ticket_id": "T-93",
|
||
"field": "status",
|
||
"old_value": "in_progress",
|
||
"new_value": "done",
|
||
"changed_at": "2026-05-07 19:04:55"
|
||
},
|
||
{
|
||
"ticket_id": "T-94",
|
||
"field": "status",
|
||
"old_value": "backlog",
|
||
"new_value": "cancelled",
|
||
"changed_at": "2026-05-07 19:08:52"
|
||
},
|
||
{
|
||
"ticket_id": "T-95",
|
||
"field": "description",
|
||
"new_value": "**File:** `lib/src/terminal/src/ui/gesture/gesture_handler.dart` (build wiring + state-method body) + `lib/src/terminal/src/ui/gesture/gesture_detector.dart` (dead public API).\n\n**Bug:** Middle-clicks (\"tertiary tap\") are routed through the wrong handler chain, and even the unreachable target carries a button-enum typo. Same shape as T-93 but on a different gesture pair.\n\nTrace:\n\n1. `TerminalGestureHandler.build` (gesture_handler.dart:65–82) wires:\n ```dart\n onTertiaryTapDown: onSecondaryTapDown,\n onTertiaryTapUp: onSecondaryTapUp,\n ```\n Right-hand sides are the state's `onSecondaryTapDown` / `onSecondaryTapUp` *methods* (lines 138–144), not the tertiary equivalents. So a middle-click reaches `_tapDown(widget.onSecondaryTapDown, details, TerminalMouseButton.right)` — i.e. fires the **secondary** callback with **button=right**.\n2. The state's `onTertiaryTapDown` and `onTertiaryTapUp` methods (lines 147–152) are therefore **dead** — they're declared but never invoked anywhere.\n3. `onTertiaryTapUp` at line 152 has a copy-paste error: `TerminalMouseButton.right` where the function is named tertiary and the comment paragraph clearly intends middle. Even if the wiring were correct, the button reported on tertiary-up would be wrong.\n4. The `onTertiaryTapDown` / `onTertiaryTapUp` *parameters* on `TerminalGestureHandler` (lines 24–25, 45–47) and `TerminalGestureDetector` (lines 13–14, 30–31) are **dead public surface** — no in-tree caller passes them, like T-93's `onTapUp`.\n\n**Two layers of \"dead\" again:**\n\n- The wiring is wrong, so middle-clicks dispatch as if they were right-clicks.\n- The public API to receive proper middle-click notifications doesn't reach any caller, so even if a consumer passes `onTertiaryTapDown:` to `TerminalView`, it would do nothing today (TerminalView doesn't expose tertiary callbacks).\n\n**User-visible impact:**\n\nA consumer relying on right-click for context-menu behaviour on `TerminalView` will accidentally trigger that menu on middle-click as well, which is wrong on every desktop platform (middle-click is conventionally paste / scroll-wheel-button). Tests of secondary-tap will pass for both right- and middle-clicks, masking the issue.\n\n**Resolutions, like T-93:**\n\n- **A. Fix the wiring + typo.** In `TerminalGestureHandler.build`, change `onTertiaryTapDown: onSecondaryTapDown` to `onTertiaryTapDown: onTertiaryTapDown` (and same for Up). Fix line 152's `TerminalMouseButton.right` → `middle`. Adds a parameter on `TerminalView` (`onTertiaryTapDown` / `onTertiaryTapUp`) so consumers can register middle-click handlers. Test surfaces the new path.\n- **B. Delete the dead surface.** Remove `onTertiaryTapDown` / `onTertiaryTapUp` parameters from both `TerminalGestureHandler` and `TerminalGestureDetector`, plus the state's tertiary methods. If middle-click ever needs distinct handling, add it back deliberately. Cleanest because no caller depends on tertiary today.\n\nRecommendation: **option B**. Same logic as T-93's recommendation — the surface is documented but unwired and unused. Adding it back later with a real consumer + tests is cheaper than fixing dead surface today.\n\n**Aside:** there's a related dead branch in `onDragStart` (line 176):\n```dart\ndetails.kind == PointerDeviceKind.mouse\n ? renderTerminal.selectCharacters(details.localPosition)\n : renderTerminal.selectWord(details.localPosition);\n```\nThe `PanGestureRecognizer` in `TerminalGestureDetector` is registered with `supportedDevices: {PointerDeviceKind.mouse}` (gesture_detector.dart:143) — so non-mouse drags can't reach `onDragStart`. The `selectWord` else-branch is unreachable through the current wiring. Consider removing it as part of the same cleanup pass, or extending PanGestureRecognizer to accept touch.\n\n**Acceptance:**\n\n- Either:\n - **A:** Tertiary tap parameters on `TerminalView` exist and fire on middle-click with `button=middle`. Regression test verifies a middle click reaches the user callback with the resolved cell offset.\n - **B:** `onTertiaryTapDown` / `onTertiaryTapUp` removed from `TerminalGestureHandler`, `TerminalGestureDetector`, and the state's tertiary methods. Existing tests still pass.\n- The `selectWord` branch in `onDragStart` is either reachable (extend PanGestureRecognizer.supportedDevices) or removed.\n\n**Surfaced from:** T-91 ui/ gesture sweep. The `onTertiaryTap*` lines (gesture_handler.dart:147-152) showed as uncovered in the lcov diff — the gestures sweep skipped writing tests for them because they're unreachable production code.\n\n**Cross-references:** T-91 (epic parent), T-93 (same shape on `onTapUp`), T-89 (coverage epic).\n",
|
||
"changed_at": "2026-05-08 11:01:01"
|
||
}
|
||
]
|
||
}
|